diff --git a/packages/shorebird_cli/lib/src/command.dart b/packages/shorebird_cli/lib/src/command.dart index bb4eaa7f..271ff8fb 100644 --- a/packages/shorebird_cli/lib/src/command.dart +++ b/packages/shorebird_cli/lib/src/command.dart @@ -43,11 +43,4 @@ abstract class ShorebirdCommand extends Command { /// [ArgResults] for the current command. ArgResults get results => testArgResults ?? argResults!; - - /// [CommandRunner] used for testing purposes only. - @visibleForTesting - CommandRunner? testCommandRunner; - - @override - CommandRunner? get runner => testCommandRunner ?? super.runner; } diff --git a/packages/shorebird_cli/lib/src/commands/build_command.dart b/packages/shorebird_cli/lib/src/commands/build_command.dart index 149d6662..ea661712 100644 --- a/packages/shorebird_cli/lib/src/commands/build_command.dart +++ b/packages/shorebird_cli/lib/src/commands/build_command.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:mason_logger/mason_logger.dart'; import 'package:shorebird_cli/src/command.dart'; +import 'package:shorebird_cli/src/shorebird_build_mixin.dart'; import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; import 'package:shorebird_cli/src/shorebird_engine_mixin.dart'; @@ -17,7 +18,7 @@ typedef RunProcess = Future Function( /// Build a new release of your application. /// {@endtemplate} class BuildCommand extends ShorebirdCommand - with ShorebirdConfigMixin, ShorebirdEngineMixin { + with ShorebirdConfigMixin, ShorebirdEngineMixin, ShorebirdBuildMixin { /// {@macro build_command} BuildCommand({ required super.logger, @@ -50,7 +51,7 @@ class BuildCommand extends ShorebirdCommand final buildProgress = logger.progress('Building release '); try { - await _build(shorebirdEnginePath); + await buildRelease(); buildProgress.complete(); } on ProcessException catch (error) { buildProgress.fail('Failed to build: ${error.message}'); @@ -59,37 +60,4 @@ class BuildCommand extends ShorebirdCommand return ExitCode.success.code; } - - Future _build(String shorebirdEnginePath) async { - const executable = 'flutter'; - final arguments = [ - 'build', - // This is temporary because the Shorebird engine currently - // only supports Android. - 'appbundle', - '--release', - '--local-engine-src-path', - shorebirdEnginePath, - '--local-engine', - // This is temporary because the Shorebird engine currently - // only supports Android arm64. - 'android_release_arm64', - ...results.rest, - ]; - - final result = await runProcess( - executable, - arguments, - runInShell: true, - ); - - if (result.exitCode != ExitCode.success.code) { - throw ProcessException( - 'flutter', - arguments, - result.stderr.toString(), - result.exitCode, - ); - } - } } diff --git a/packages/shorebird_cli/lib/src/commands/publish_command.dart b/packages/shorebird_cli/lib/src/commands/publish_command.dart index b5a5705d..dc9ded38 100644 --- a/packages/shorebird_cli/lib/src/commands/publish_command.dart +++ b/packages/shorebird_cli/lib/src/commands/publish_command.dart @@ -1,21 +1,27 @@ import 'dart:io'; +import 'package:collection/collection.dart'; import 'package:mason_logger/mason_logger.dart'; import 'package:path/path.dart' as p; import 'package:shorebird_cli/src/command.dart'; +import 'package:shorebird_cli/src/shorebird_build_mixin.dart'; import 'package:shorebird_cli/src/shorebird_config_mixin.dart'; +import 'package:shorebird_cli/src/shorebird_engine_mixin.dart'; +import 'package:shorebird_code_push_client/shorebird_code_push_client.dart'; /// {@template publish_command} /// /// `shorebird publish ` /// Publish new releases to the Shorebird CodePush server. /// {@endtemplate} -class PublishCommand extends ShorebirdCommand with ShorebirdConfigMixin { +class PublishCommand extends ShorebirdCommand + with ShorebirdConfigMixin, ShorebirdEngineMixin, ShorebirdBuildMixin { /// {@macro publish_command} PublishCommand({ required super.logger, super.auth, super.buildCodePushClient, + super.runProcess, }); @override @@ -39,55 +45,123 @@ class PublishCommand extends ShorebirdCommand with ShorebirdConfigMixin { return ExitCode.noUser.code; } - final args = results.rest; - if (args.length > 1) { - usageException('A single file path must be specified.'); - } - - final artifactPath = args.isEmpty - ? p.join( - Directory.current.path, - 'build', - 'app', - 'intermediates', - 'stripped_native_libs', - 'release', - 'out', - 'lib', - 'arm64-v8a', - 'libapp.so', - ) - : args.first; - - final artifact = File(artifactPath); - if (!artifact.existsSync()) { - logger.err('Artifact not found: "${artifact.path}"'); - return ExitCode.noInput.code; - } - try { - final pubspecYaml = getPubspecYaml()!; - final shorebirdYaml = getShorebirdYaml()!; - final codePushClient = buildCodePushClient( - apiKey: session.apiKey, - hostedUri: hostedUri, - ); - logger.detail( - '''Deploying ${artifact.path} to ${shorebirdYaml.appId} (${pubspecYaml.version})''', - ); - final version = pubspecYaml.version!; - await codePushClient.createPatch( - artifactPath: artifact.path, - releaseVersion: '${version.major}.${version.minor}.${version.patch}', - appId: shorebirdYaml.appId, - channel: 'stable', - ); + await ensureEngineExists(); } catch (error) { - logger.err('$error'); + logger.err(error.toString()); return ExitCode.software.code; } - logger.success('Successfully deployed.'); + final buildProgress = logger.progress('Building release'); + try { + await buildRelease(); + buildProgress.complete(); + } on ProcessException catch (error) { + buildProgress.fail('Failed to build: ${error.message}'); + return ExitCode.software.code; + } + + final artifactPath = p.join( + Directory.current.path, + 'build', + 'app', + 'intermediates', + 'stripped_native_libs', + 'release', + 'out', + 'lib', + 'arm64-v8a', + 'libapp.so', + ); + + final artifact = File(artifactPath); + + if (!artifact.existsSync()) { + logger.err('Artifact not found: "${artifact.path}"'); + return ExitCode.software.code; + } + + final pubspecYaml = getPubspecYaml()!; + final shorebirdYaml = getShorebirdYaml()!; + final codePushClient = buildCodePushClient( + apiKey: session.apiKey, + hostedUri: hostedUri, + ); + final version = pubspecYaml.version!; + final versionString = '${version.major}.${version.minor}.${version.patch}'; + + logger.info( + ''' +Ready to publish the following patch: +App: ${pubspecYaml.name} (${shorebirdYaml.appId}) +Release Version: $versionString +Patch Number: [NEW] +''', + ); + + final confirm = logger.confirm('Are you sure you want to continue?'); + + if (!confirm) { + logger.info('Aborting.'); + return ExitCode.success.code; + } + + late final List releases; + final fetchReleasesProgress = logger.progress('Fetching releases'); + try { + releases = await codePushClient.getReleases( + appId: shorebirdYaml.appId, + ); + fetchReleasesProgress.complete(); + } catch (error) { + fetchReleasesProgress.fail('$error'); + return ExitCode.software.code; + } + + var release = releases.firstWhereOrNull( + (r) => r.version == versionString, + ); + + if (release == null) { + final createReleaseProgress = logger.progress('Creating release'); + try { + release = await codePushClient.createRelease( + appId: shorebirdYaml.appId, + version: versionString, + ); + createReleaseProgress.complete(); + } catch (error) { + createReleaseProgress.fail('$error'); + return ExitCode.software.code; + } + } + + late final Patch patch; + final createPatchProgress = logger.progress('Creating patch'); + try { + patch = await codePushClient.createPatch(releaseId: release.id); + createPatchProgress.complete(); + } catch (error) { + createPatchProgress.fail('$error'); + return ExitCode.software.code; + } + + final createArtifactProgress = logger.progress('Creating artifact'); + try { + await codePushClient.createArtifact( + patchId: patch.id, + artifactPath: artifact.path, + arch: 'aarch64', + platform: 'android', + hash: '#', + ); + createArtifactProgress.complete(); + } catch (error) { + createArtifactProgress.fail('$error'); + return ExitCode.software.code; + } + + logger.success('Published!'); return ExitCode.success.code; } } diff --git a/packages/shorebird_cli/lib/src/shorebird_build_mixin.dart b/packages/shorebird_cli/lib/src/shorebird_build_mixin.dart new file mode 100644 index 00000000..ca9d4244 --- /dev/null +++ b/packages/shorebird_cli/lib/src/shorebird_build_mixin.dart @@ -0,0 +1,39 @@ +import 'dart:io'; + +import 'package:mason_logger/mason_logger.dart'; +import 'package:shorebird_cli/src/shorebird_engine_mixin.dart'; + +mixin ShorebirdBuildMixin on ShorebirdEngineMixin { + Future buildRelease() async { + const executable = 'flutter'; + final arguments = [ + 'build', + // This is temporary because the Shorebird engine currently + // only supports Android. + 'appbundle', + '--release', + '--local-engine-src-path', + shorebirdEnginePath, + '--local-engine', + // This is temporary because the Shorebird engine currently + // only supports Android arm64. + 'android_release_arm64', + ...results.rest, + ]; + + final result = await runProcess( + executable, + arguments, + runInShell: true, + ); + + if (result.exitCode != ExitCode.success.code) { + throw ProcessException( + 'flutter', + arguments, + result.stderr.toString(), + result.exitCode, + ); + } + } +} diff --git a/packages/shorebird_cli/lib/src/shorebird_engine_mixin.dart b/packages/shorebird_cli/lib/src/shorebird_engine_mixin.dart index 14bd90a6..44baa1f0 100644 --- a/packages/shorebird_cli/lib/src/shorebird_engine_mixin.dart +++ b/packages/shorebird_cli/lib/src/shorebird_engine_mixin.dart @@ -67,7 +67,7 @@ mixin ShorebirdEngineMixin on ShorebirdConfigMixin { String path, ) async { final engine = await codePushClient.downloadEngine( - requiredFlutterEngineRevision, + revision: requiredFlutterEngineRevision, ); final targetFile = File(path); diff --git a/packages/shorebird_cli/test/src/commands/build_command_test.dart b/packages/shorebird_cli/test/src/commands/build_command_test.dart index ac0b21c8..eafe8637 100644 --- a/packages/shorebird_cli/test/src/commands/build_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/build_command_test.dart @@ -52,7 +52,7 @@ void main() { when(() => argResults.rest).thenReturn([]); when( - () => codePushClient.downloadEngine(any()), + () => codePushClient.downloadEngine(revision: any(named: 'revision')), ).thenAnswer((_) async => Uint8List.fromList([])); when(() => logger.progress(any())).thenReturn(_MockProgress()); }); @@ -71,7 +71,7 @@ void main() { test('exits with code 70 when pulling engine fails', () async { when( - () => codePushClient.downloadEngine(any()), + () => codePushClient.downloadEngine(revision: any(named: 'revision')), ).thenThrow(Exception('oops')); when(() => auth.currentSession).thenReturn(session); @@ -103,7 +103,7 @@ void main() { '${tempDir.path}/.shorebird/engine', ).createSync(recursive: true); when( - () => codePushClient.downloadEngine(any()), + () => codePushClient.downloadEngine(revision: any(named: 'revision')), ).thenAnswer((_) async => Uint8List.fromList([])); when(() => auth.currentSession).thenReturn(session); diff --git a/packages/shorebird_cli/test/src/commands/publish_command_test.dart b/packages/shorebird_cli/test/src/commands/publish_command_test.dart index 7e98e5ea..4fd3b869 100644 --- a/packages/shorebird_cli/test/src/commands/publish_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/publish_command_test.dart @@ -1,7 +1,7 @@ import 'dart:io'; +import 'dart:typed_data'; import 'package:args/args.dart'; -import 'package:args/command_runner.dart'; import 'package:mason_logger/mason_logger.dart'; import 'package:mocktail/mocktail.dart'; import 'package:path/path.dart' as p; @@ -19,18 +19,30 @@ class _MockLogger extends Mock implements Logger {} class _MockProgress extends Mock implements Progress {} -class _MockCodePushClient extends Mock implements CodePushClient {} +class _MockProcessResult extends Mock implements ProcessResult {} -class _FakeCommandRunner extends Fake implements CommandRunner { - @override - String get executableName => 'shorebird_test'; -} +class _MockCodePushClient extends Mock implements CodePushClient {} void main() { group('publish', () { const session = Session(apiKey: 'test-api-key'); const appId = 'test-app-id'; const version = '1.2.3'; + const artifact = Artifact( + id: 0, + patchId: 0, + arch: 'aarch64', + platform: 'android', + hash: '#', + url: 'https://example.com', + ); + const release = Release( + id: 0, + appId: appId, + version: version, + displayName: '1.2.3', + ); + const patch = Patch(id: 0, number: 1); const pubspecYamlContent = ''' name: example version: $version @@ -43,7 +55,9 @@ flutter: late ArgResults argResults; late Auth auth; + late Progress progress; late Logger logger; + late ProcessResult processResult; late CodePushClient codePushClient; late PublishCommand command; late Uri? capturedHostedUri; @@ -62,7 +76,9 @@ flutter: setUp(() { argResults = _MockArgResults(); auth = _MockAuth(); + progress = _MockProgress(); logger = _MockLogger(); + processResult = _MockProcessResult(); codePushClient = _MockCodePushClient(); command = PublishCommand( auth: auth, @@ -70,22 +86,41 @@ flutter: capturedHostedUri = hostedUri; return codePushClient; }, + runProcess: (executable, arguments, {bool runInShell = false}) async { + return processResult; + }, logger: logger, - ) - ..testArgResults = argResults - ..testCommandRunner = _FakeCommandRunner(); + )..testArgResults = argResults; when(() => argResults.rest).thenReturn([]); when(() => auth.currentSession).thenReturn(session); - when(() => logger.progress(any())).thenReturn(_MockProgress()); + when(() => logger.progress(any())).thenReturn(progress); + when(() => logger.confirm(any())).thenReturn(true); + when(() => processResult.exitCode).thenReturn(ExitCode.success.code); when( - () => codePushClient.createPatch( - releaseVersion: any(named: 'releaseVersion'), - artifactPath: any(named: 'artifactPath'), - channel: any(named: 'channel'), + () => codePushClient.downloadEngine(revision: any(named: 'revision')), + ).thenAnswer((_) async => Uint8List.fromList([])); + when( + () => codePushClient.getReleases(appId: any(named: 'appId')), + ).thenAnswer((_) async => [release]); + when( + () => codePushClient.createRelease( appId: any(named: 'appId'), + version: any(named: 'version'), ), - ).thenAnswer((_) async {}); + ).thenAnswer((_) async => release); + when( + () => codePushClient.createPatch(releaseId: any(named: 'releaseId')), + ).thenAnswer((_) async => patch); + when( + () => codePushClient.createArtifact( + artifactPath: any(named: 'artifactPath'), + patchId: any(named: 'patchId'), + arch: any(named: 'arch'), + platform: any(named: 'platform'), + hash: any(named: 'hash'), + ), + ).thenAnswer((_) async => artifact); }); test('throws config error when shorebird is not initialized', () async { @@ -112,21 +147,48 @@ flutter: expect(exitCode, equals(ExitCode.noUser.code)); }); - test('throws usage error when multiple args are passed.', () async { - when(() => argResults.rest).thenReturn(['arg1', 'arg2']); + test('exits with code 70 when pulling engine fails', () async { + when( + () => codePushClient.downloadEngine(revision: any(named: 'revision')), + ).thenThrow(Exception('oops')); + when(() => auth.currentSession).thenReturn(session); final tempDir = setUpTempDir(); - await expectLater( - IOOverrides.runZoned( - () => command.run(), - getCurrentDirectory: () => tempDir, - ), - throwsA(isA()), + final exitCode = await IOOverrides.runZoned( + command.run, + getCurrentDirectory: () => tempDir, ); + expect(exitCode, equals(ExitCode.software.code)); }); - test('throws no input error when artifact is not found (default).', + test('exits with code 70 when building fails', () async { + when(() => processResult.exitCode).thenReturn(1); + when(() => processResult.stderr).thenReturn('oops'); + when(() => auth.currentSession).thenReturn(session); + + final tempDir = setUpTempDir(); + Directory( + '${tempDir.path}/.shorebird/engine', + ).createSync(recursive: true); + Directory( + '${tempDir.path}/.shorebird/cache', + ).createSync(recursive: true); + final exitCode = await IOOverrides.runZoned( + () async => command.run(), + getCurrentDirectory: () => tempDir, + ); + + expect(exitCode, equals(ExitCode.software.code)); + }); + + test('throws software error when artifact is not found (default).', () async { final tempDir = setUpTempDir(); + Directory( + '${tempDir.path}/.shorebird/engine', + ).createSync(recursive: true); + Directory( + '${tempDir.path}/.shorebird/cache', + ).createSync(recursive: true); final exitCode = await IOOverrides.runZoned( command.run, getCurrentDirectory: () => tempDir, @@ -134,66 +196,216 @@ flutter: verify( () => logger.err(any(that: contains('Artifact not found:'))), ).called(1); - expect(exitCode, ExitCode.noInput.code); - }); - - test('throws no input error when artifact is not found (custom).', - () async { - final tempDir = setUpTempDir(); - final artifact = File(p.join(tempDir.path, 'patch.txt')); - when(() => argResults.rest).thenReturn([artifact.path]); - final exitCode = await IOOverrides.runZoned( - command.run, - getCurrentDirectory: () => tempDir, - ); - verify( - () => logger.err( - any( - that: contains('Artifact not found: "${artifact.path}"'), - ), - ), - ).called(1); - expect(exitCode, ExitCode.noInput.code); - }); - - test('throws error when publish fails.', () async { - const error = 'something went wrong'; - when( - () => codePushClient.createPatch( - releaseVersion: any(named: 'releaseVersion'), - artifactPath: any(named: 'artifactPath'), - channel: any(named: 'channel'), - appId: any(named: 'appId'), - ), - ).thenThrow(error); - final tempDir = setUpTempDir(); - final artifact = File(p.join(tempDir.path, 'patch.txt'))..createSync(); - when(() => argResults.rest).thenReturn([artifact.path]); - final exitCode = await IOOverrides.runZoned( - command.run, - getCurrentDirectory: () => tempDir, - ); - verify(() => logger.err(error)).called(1); expect(exitCode, ExitCode.software.code); }); - test('succeeds when publish is successful using existing app id', () async { + test('aborts when user opts out', () async { + when(() => logger.confirm(any())).thenReturn(false); final tempDir = setUpTempDir(); - final artifact = File(p.join(tempDir.path, 'patch.txt'))..createSync(); - when(() => argResults.rest).thenReturn([artifact.path]); + Directory( + '${tempDir.path}/.shorebird/engine', + ).createSync(recursive: true); + Directory( + '${tempDir.path}/.shorebird/cache', + ).createSync(recursive: true); + final artifactPath = p.join( + tempDir.path, + 'build', + 'app', + 'intermediates', + 'stripped_native_libs', + 'release', + 'out', + 'lib', + 'arm64-v8a', + 'libapp.so', + ); + File(artifactPath).createSync(recursive: true); final exitCode = await IOOverrides.runZoned( command.run, getCurrentDirectory: () => tempDir, ); - verify(() => logger.success('Successfully deployed.')).called(1); - verify( - () => codePushClient.createPatch( - releaseVersion: version, - appId: appId, - artifactPath: artifact.path, - channel: 'stable', + expect(exitCode, ExitCode.success.code); + verify(() => logger.info('Aborting.')).called(1); + }); + + test('throws error when fetching releases fails.', () async { + const error = 'something went wrong'; + when( + () => codePushClient.getReleases(appId: any(named: 'appId')), + ).thenThrow(error); + final tempDir = setUpTempDir(); + Directory( + '${tempDir.path}/.shorebird/engine', + ).createSync(recursive: true); + Directory( + '${tempDir.path}/.shorebird/cache', + ).createSync(recursive: true); + final artifactPath = p.join( + tempDir.path, + 'build', + 'app', + 'intermediates', + 'stripped_native_libs', + 'release', + 'out', + 'lib', + 'arm64-v8a', + 'libapp.so', + ); + File(artifactPath).createSync(recursive: true); + final exitCode = await IOOverrides.runZoned( + command.run, + getCurrentDirectory: () => tempDir, + ); + verify(() => progress.fail(error)).called(1); + expect(exitCode, ExitCode.software.code); + }); + + test('throws error when creating release fails.', () async { + const error = 'something went wrong'; + when( + () => codePushClient.getReleases(appId: any(named: 'appId')), + ).thenAnswer((_) async => []); + when( + () => codePushClient.createRelease( + appId: any(named: 'appId'), + version: any(named: 'version'), + displayName: any(named: 'displayName'), ), - ).called(1); + ).thenThrow(error); + final tempDir = setUpTempDir(); + Directory( + '${tempDir.path}/.shorebird/engine', + ).createSync(recursive: true); + Directory( + '${tempDir.path}/.shorebird/cache', + ).createSync(recursive: true); + final artifactPath = p.join( + tempDir.path, + 'build', + 'app', + 'intermediates', + 'stripped_native_libs', + 'release', + 'out', + 'lib', + 'arm64-v8a', + 'libapp.so', + ); + File(artifactPath).createSync(recursive: true); + final exitCode = await IOOverrides.runZoned( + command.run, + getCurrentDirectory: () => tempDir, + ); + verify(() => progress.fail(error)).called(1); + expect(exitCode, ExitCode.software.code); + }); + + test('throws error when creating patch fails.', () async { + const error = 'something went wrong'; + when( + () => codePushClient.getReleases(appId: any(named: 'appId')), + ).thenAnswer((_) async => []); + when( + () => codePushClient.createPatch(releaseId: any(named: 'releaseId')), + ).thenThrow(error); + final tempDir = setUpTempDir(); + Directory( + '${tempDir.path}/.shorebird/engine', + ).createSync(recursive: true); + Directory( + '${tempDir.path}/.shorebird/cache', + ).createSync(recursive: true); + final artifactPath = p.join( + tempDir.path, + 'build', + 'app', + 'intermediates', + 'stripped_native_libs', + 'release', + 'out', + 'lib', + 'arm64-v8a', + 'libapp.so', + ); + File(artifactPath).createSync(recursive: true); + final exitCode = await IOOverrides.runZoned( + command.run, + getCurrentDirectory: () => tempDir, + ); + verify(() => progress.fail(error)).called(1); + expect(exitCode, ExitCode.software.code); + }); + + test('throws error when uploading artifact fails.', () async { + const error = 'something went wrong'; + when( + () => codePushClient.getReleases(appId: any(named: 'appId')), + ).thenAnswer((_) async => []); + when( + () => codePushClient.createArtifact( + artifactPath: any(named: 'artifactPath'), + patchId: any(named: 'patchId'), + arch: any(named: 'arch'), + platform: any(named: 'platform'), + hash: any(named: 'hash'), + ), + ).thenThrow(error); + final tempDir = setUpTempDir(); + Directory( + '${tempDir.path}/.shorebird/engine', + ).createSync(recursive: true); + Directory( + '${tempDir.path}/.shorebird/cache', + ).createSync(recursive: true); + final artifactPath = p.join( + tempDir.path, + 'build', + 'app', + 'intermediates', + 'stripped_native_libs', + 'release', + 'out', + 'lib', + 'arm64-v8a', + 'libapp.so', + ); + File(artifactPath).createSync(recursive: true); + final exitCode = await IOOverrides.runZoned( + command.run, + getCurrentDirectory: () => tempDir, + ); + verify(() => progress.fail(error)).called(1); + expect(exitCode, ExitCode.software.code); + }); + + test('succeeds when publish is successful', () async { + final tempDir = setUpTempDir(); + Directory( + '${tempDir.path}/.shorebird/engine', + ).createSync(recursive: true); + Directory( + '${tempDir.path}/.shorebird/cache', + ).createSync(recursive: true); + final artifactPath = p.join( + tempDir.path, + 'build', + 'app', + 'intermediates', + 'stripped_native_libs', + 'release', + 'out', + 'lib', + 'arm64-v8a', + 'libapp.so', + ); + File(artifactPath).createSync(recursive: true); + final exitCode = await IOOverrides.runZoned( + command.run, + getCurrentDirectory: () => tempDir, + ); + verify(() => logger.success('Published!')).called(1); expect(exitCode, ExitCode.success.code); expect(capturedHostedUri, isNull); }); @@ -208,8 +420,25 @@ flutter: app_id: $appId base_url: $baseUrl''', ); - final artifact = File(p.join(tempDir.path, 'patch.txt'))..createSync(); - when(() => argResults.rest).thenReturn([artifact.path]); + Directory( + '${tempDir.path}/.shorebird/engine', + ).createSync(recursive: true); + Directory( + '${tempDir.path}/.shorebird/cache', + ).createSync(recursive: true); + final artifactPath = p.join( + tempDir.path, + 'build', + 'app', + 'intermediates', + 'stripped_native_libs', + 'release', + 'out', + 'lib', + 'arm64-v8a', + 'libapp.so', + ); + File(artifactPath).createSync(recursive: true); await IOOverrides.runZoned( command.run, getCurrentDirectory: () => tempDir, diff --git a/packages/shorebird_cli/test/src/commands/run_command_test.dart b/packages/shorebird_cli/test/src/commands/run_command_test.dart index fcfbca8a..d25c636f 100644 --- a/packages/shorebird_cli/test/src/commands/run_command_test.dart +++ b/packages/shorebird_cli/test/src/commands/run_command_test.dart @@ -74,7 +74,7 @@ void main() { final error = Exception('oops'); when(() => auth.currentSession).thenReturn(session); when( - () => codePushClient.downloadEngine(any()), + () => codePushClient.downloadEngine(revision: any(named: 'revision')), ).thenThrow(error); final progress = _MockProgress(); when(() => logger.progress(any())).thenReturn(progress); @@ -98,7 +98,7 @@ void main() { when(() => auth.currentSession).thenReturn(session); when( - () => codePushClient.downloadEngine(any()), + () => codePushClient.downloadEngine(revision: any(named: 'revision')), ).thenAnswer((_) async => Uint8List(0)); final progress = _MockProgress(); when(() => logger.progress(any())).thenReturn(progress); @@ -129,7 +129,7 @@ void main() { when(() => auth.currentSession).thenReturn(session); when( - () => codePushClient.downloadEngine(any()), + () => codePushClient.downloadEngine(revision: any(named: 'revision')), ).thenAnswer((_) async => Uint8List(0)); final progress = _MockProgress(); @@ -163,7 +163,7 @@ void main() { .createSync(recursive: true); when(() => auth.currentSession).thenReturn(session); when( - () => codePushClient.downloadEngine(any()), + () => codePushClient.downloadEngine(revision: any(named: 'revision')), ).thenAnswer((_) async => Uint8List(0)); final progress = _MockProgress();