Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(shorebird_cli): publish using shorebird_code_push_api_client #6

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions packages/shorebird_cli/lib/src/command_runner.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import 'package:args/args.dart';
import 'package:args/command_runner.dart';
import 'package:cli_completion/cli_completion.dart';
import 'package:http/http.dart' as http;
import 'package:mason_logger/mason_logger.dart';
import 'package:pub_updater/pub_updater.dart';
import 'package:shorebird_cli/src/commands/commands.dart';
import 'package:shorebird_cli/src/version.dart';
import 'package:shorebird_code_push_api_client/shorebird_code_push_api_client.dart';

const executableName = 'shorebird';
const packageName = 'shorebird_cli';
Expand All @@ -22,7 +22,7 @@ class ShorebirdCliCommandRunner extends CompletionCommandRunner<int> {
/// {@macro shorebird_cli_command_runner}
ShorebirdCliCommandRunner({
Logger? logger,
http.Client? httpClient,
ShorebirdCodePushApiClient? codePushApiClient,
PubUpdater? pubUpdater,
}) : _logger = logger ?? Logger(),
_pubUpdater = pubUpdater ?? PubUpdater(),
Expand All @@ -41,7 +41,9 @@ class ShorebirdCliCommandRunner extends CompletionCommandRunner<int> {
);

// Add sub commands
addCommand(PublishCommand(logger: _logger, httpClient: httpClient));
addCommand(
PublishCommand(logger: _logger, codePushApiClient: codePushApiClient),
);
addCommand(UpdateCommand(logger: _logger, pubUpdater: _pubUpdater));
}

Expand Down
37 changes: 15 additions & 22 deletions packages/shorebird_cli/lib/src/commands/publish_command.dart
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import 'dart:io';

import 'package:args/command_runner.dart';
import 'package:http/http.dart' as http;
import 'package:mason_logger/mason_logger.dart';
import 'package:shorebird_code_push_api_client/shorebird_code_push_api_client.dart';

/// {@template sample_command}
/// {@template publish_command}
///
/// `shorebird sample`
/// A [Command] to exemplify a sub command
/// `shorebird publish <path/to/artifact>`
/// Publish new releases to the Shorebird CodePush server.
/// {@endtemplate}
class PublishCommand extends Command<int> {
/// {@macro sample_command}
PublishCommand({required Logger logger, http.Client? httpClient})
: _logger = logger,
_httpClient = httpClient ?? http.Client();
/// {@macro publish_command}
PublishCommand({
required Logger logger,
ShorebirdCodePushApiClient? codePushApiClient,
}) : _logger = logger,
_codePushApiClient = codePushApiClient ?? ShorebirdCodePushApiClient();

@override
String get description => 'Publish an update.';
Expand All @@ -22,7 +24,7 @@ class PublishCommand extends Command<int> {
String get name => 'publish';

final Logger _logger;
final http.Client _httpClient;
final ShorebirdCodePushApiClient _codePushApiClient;

@override
Future<int> run() async {
Expand All @@ -37,23 +39,14 @@ class PublishCommand extends Command<int> {
return ExitCode.noInput.code;
}

final request = http.MultipartRequest(
'POST',
Uri.parse('http://localhost:8080/api/v1/releases'),
);
final file = await http.MultipartFile.fromPath('file', artifact.path);
request.files.add(file);
final response = await _httpClient.send(request);

if (response.statusCode != HttpStatus.created) {
_logger.err(
'Failed to deploy: ${response.statusCode} ${response.reasonPhrase}',
);
try {
await _codePushApiClient.createRelease(artifact.path);
} catch (error) {
_logger.err('Failed to deploy: $error');
return ExitCode.software.code;
}

_logger.success('Deployed ${artifact.path}!');

return ExitCode.success.code;
}
}
5 changes: 4 additions & 1 deletion packages/shorebird_cli/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,19 @@ name: shorebird_cli
description: The shorebird command-line tool
version: 0.0.1

publish_to: none

environment:
sdk: ">=2.19.0 <3.0.0"

dependencies:
args: ^2.3.1
cli_completion: ^0.2.0
http: ^0.13.5
mason_logger: ^0.2.4
path: ^1.8.3
pub_updater: ^0.2.4
shorebird_code_push_api_client:
path: ../shorebird_code_push_api_client

dev_dependencies:
build_runner: ^2.0.0
Expand Down
43 changes: 12 additions & 31 deletions packages/shorebird_cli/test/src/commands/publish_command_test.dart
Original file line number Diff line number Diff line change
@@ -1,34 +1,27 @@
import 'dart:io';

import 'package:http/http.dart' as http;
import 'package:mason_logger/mason_logger.dart';
import 'package:mocktail/mocktail.dart';
import 'package:path/path.dart' as p;
import 'package:shorebird_cli/src/command_runner.dart';
import 'package:shorebird_code_push_api_client/shorebird_code_push_api_client.dart';
import 'package:test/test.dart';

class _MockLogger extends Mock implements Logger {}

class _MockHttpClient extends Mock implements http.Client {}

class _FakeBaseRequest extends Fake implements http.BaseRequest {}
class _MockShorebirdCodePushApiClient extends Mock
implements ShorebirdCodePushApiClient {}

void main() {
group('publish', () {
late Logger logger;
late http.Client httpClient;
late _MockShorebirdCodePushApiClient codePushApiClient;
late ShorebirdCliCommandRunner commandRunner;

setUpAll(() {
registerFallbackValue(_FakeBaseRequest());
});

setUp(() {
logger = _MockLogger();
httpClient = _MockHttpClient();
codePushApiClient = _MockShorebirdCodePushApiClient();
commandRunner = ShorebirdCliCommandRunner(
logger: logger,
httpClient: httpClient,
codePushApiClient: codePushApiClient,
);
});

Expand Down Expand Up @@ -58,30 +51,18 @@ void main() {
});

test('throws error when release fails.', () async {
const statusCode = HttpStatus.internalServerError;
const reasonPhrase = 'something went wrong';
when(() => httpClient.send(any())).thenAnswer(
(_) async => http.StreamedResponse(
const Stream.empty(),
statusCode,
reasonPhrase: reasonPhrase,
),
);
const error = 'something went wrong';
when(() => codePushApiClient.createRelease(any())).thenThrow(error);
final release = p.join('test', 'fixtures', 'release.txt');
final exitCode = await commandRunner.run(['publish', release]);
verify(
() => logger.err('Failed to deploy: $statusCode $reasonPhrase'),
).called(1);
verify(() => logger.err('Failed to deploy: $error')).called(1);
expect(exitCode, ExitCode.software.code);
});

test('succeeds when release is successful.', () async {
when(() => httpClient.send(any())).thenAnswer(
(_) async => http.StreamedResponse(
const Stream.empty(),
HttpStatus.created,
),
);
when(
() => codePushApiClient.createRelease(any()),
).thenAnswer((_) async {});
final release = p.join('test', 'fixtures', 'release.txt');
final exitCode = await commandRunner.run(['publish', release]);
verify(() => logger.success('Deployed $release!')).called(1);
Expand Down