feat(shorebird_cli): shorebird preview stream logs on MacOS (#2688)

This commit is contained in:
Felix Angelov
2024-12-16 14:41:57 -06:00
committed by GitHub
parent 61756da7b0
commit 11cef956b7
7 changed files with 113 additions and 20 deletions
@@ -75,6 +75,7 @@ Command: shorebird ${args.join(' ')}
javaRef,
loggerRef,
networkCheckerRef,
openRef,
osInterfaceRef,
patchExecutableRef,
patchDiffCheckerRef,
@@ -21,7 +21,6 @@ import 'package:shorebird_cli/src/logging/logging.dart';
import 'package:shorebird_cli/src/platform.dart';
import 'package:shorebird_cli/src/shorebird_command.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_process.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_cli/src/third_party/flutter_tools/lib/flutter_tools.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
@@ -339,9 +338,15 @@ class PreviewCommand extends ShorebirdCommand {
}
}
// TODO(felangel): wrap `open` and stream logs.
final proc = await process.start('open', ['-n', appDirectory.path]);
return proc.exitCode;
final logs = await open.newApplication(path: appDirectory.path);
final completer = Completer<void>();
logs.listen(
(log) => logger.info(utf8.decode(log)),
onDone: completer.complete,
);
return completer.future.then((_) => ExitCode.success.code);
}
Future<int> installAndLaunchAndroid({
@@ -10,6 +10,7 @@ export 'gradlew.dart';
export 'idevicesyslog.dart';
export 'ios_deploy.dart';
export 'java.dart';
export 'open.dart';
export 'patch_executable.dart';
export 'shorebird_tools.dart';
export 'xcodebuild.dart';
@@ -0,0 +1,39 @@
import 'dart:async';
import 'dart:io';
import 'package:path/path.dart' as p;
import 'package:scoped_deps/scoped_deps.dart';
import 'package:shorebird_cli/src/shorebird_process.dart';
/// A reference to a [Open] instance.
final openRef = create(Open.new);
/// The [Open] instance available in the current zone.
Open get open => read(openRef);
/// A wrapper around the `open` command.
/// https://ss64.com/mac/open.html
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 tmp = Directory.systemTemp.createTempSync();
final stdout = File(p.join(tmp.path, 'stdout.log'))..createSync();
await process.start(
'open',
[
'-n',
path,
'--stdout=${stdout.path}',
'--stderr=${stdout.path}',
],
);
final stdoutProcess = await process.start(
'tail',
['-fn+1', stdout.path],
);
return stdoutProcess.stdout;
}
}
@@ -22,7 +22,6 @@ import 'package:shorebird_cli/src/http_client/http_client.dart';
import 'package:shorebird_cli/src/logging/logging.dart';
import 'package:shorebird_cli/src/platform.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_process.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
import 'package:test/test.dart';
@@ -1518,8 +1517,7 @@ channel: ${DeploymentTrack.staging.channel}
const releasePlatform = ReleasePlatform.macos;
late Ditto ditto;
late ShorebirdProcess shorebirdProcess;
late Process process;
late Open open;
R runWithOverrides<R>(R Function() body) {
return HttpOverrides.runZoned(
@@ -1537,7 +1535,7 @@ channel: ${DeploymentTrack.staging.channel}
httpClientRef.overrideWith(() => httpClient),
loggerRef.overrideWith(() => logger),
platformRef.overrideWith(() => platform),
processRef.overrideWith(() => shorebirdProcess),
openRef.overrideWith(() => open),
shorebirdEnvRef.overrideWith(() => shorebirdEnv),
shorebirdValidatorRef.overrideWith(() => shorebirdValidator),
},
@@ -1547,8 +1545,7 @@ channel: ${DeploymentTrack.staging.channel}
setUp(() {
ditto = MockDitto();
shorebirdProcess = MockShorebirdProcess();
process = MockProcess();
open = MockOpen();
when(() => releaseArtifact.id).thenReturn(macosArtifactId);
when(() => argResults['platform']).thenReturn(releasePlatform.name);
@@ -1572,9 +1569,9 @@ channel: ${DeploymentTrack.staging.channel}
});
when(() => releaseArtifact.url).thenReturn(releaseArtifactUrl);
when(() => platform.isMacOS).thenReturn(true);
when(() => shorebirdProcess.start(any(), any())).thenAnswer(
(_) async => process,
);
when(
() => open.newApplication(path: any(named: 'path')),
).thenAnswer((_) async => Stream.value(utf8.encode('hello world')));
});
group('when querying for release artifact fails', () {
@@ -1638,16 +1635,10 @@ channel: ${DeploymentTrack.staging.channel}
});
group('when process completes with exit code 0', () {
setUp(() {
when(() => process.exitCode).thenAnswer((_) async => 0);
});
test('completes successfully', () async {
final result = await runWithOverrides(command.run);
expect(result, equals(ExitCode.success.code));
verify(
() => shorebirdProcess.start('open', any()),
).called(1);
verify(() => logger.info('hello world')).called(1);
});
});
});
@@ -0,0 +1,54 @@
import 'dart:convert';
import 'package:mocktail/mocktail.dart';
import 'package:scoped_deps/scoped_deps.dart';
import 'package:shorebird_cli/src/executables/executables.dart';
import 'package:shorebird_cli/src/shorebird_process.dart';
import 'package:test/test.dart';
import '../mocks.dart';
void main() {
group(Open, () {
late Open open;
late ShorebirdProcess process;
R runWithOverrides<R>(R Function() body) {
return runScoped(
body,
values: {
processRef.overrideWith(() => process),
},
);
}
setUp(() {
process = MockShorebirdProcess();
open = Open();
});
group('newApplication', () {
test('executes correct command and streams logs', () async {
final openProcess = MockProcess();
final tailProcess = MockProcess();
when(() => process.start('open', any())).thenAnswer((_) async {
return openProcess;
});
when(() => process.start('tail', any())).thenAnswer((_) async {
return tailProcess;
});
when(() => tailProcess.stdout).thenAnswer(
(_) => Stream.fromIterable([utf8.encode('hello world') as List<int>]),
);
final stream = await runWithOverrides(
() => open.newApplication(path: 'test'),
);
expect(stream, emits(utf8.encode('hello world')));
});
});
});
}
@@ -117,6 +117,8 @@ class MockJwtPayload extends Mock implements JwtPayload {}
class MockNetworkChecker extends Mock implements NetworkChecker {}
class MockOpen extends Mock implements Open {}
class MockOperatingSystemInterface extends Mock
implements OperatingSystemInterface {}