feat(shorebird_cli): shorebird channels list (#201)
This commit is contained in:
@@ -239,6 +239,29 @@ Build a new release of your application using the `shorebird build` command:
|
||||
shorebird build
|
||||
```
|
||||
|
||||
### List Channels
|
||||
|
||||
See all available channels for your application using the `shorebird channels list` command:
|
||||
|
||||
```bash
|
||||
shorebird channels list
|
||||
```
|
||||
|
||||
**Sample**
|
||||
|
||||
```
|
||||
shorebird channels list
|
||||
📱 App ID: 61fc9c16-3c4a-4825-a155-9765993614aa
|
||||
📺 Channels
|
||||
┌─────────────┐
|
||||
│ Name │
|
||||
├─────────────┤
|
||||
│ stable │
|
||||
├─────────────┤
|
||||
│ development │
|
||||
└─────────────┘
|
||||
```
|
||||
|
||||
### Usage
|
||||
|
||||
```
|
||||
@@ -252,15 +275,16 @@ Global options:
|
||||
--[no-]verbose Noisy logging, including all shell commands executed.
|
||||
|
||||
Available commands:
|
||||
apps Manage your Shorebird apps.
|
||||
build Build a new release of your application.
|
||||
init Initialize Shorebird.
|
||||
login Login as a new Shorebird user.
|
||||
logout Logout of the current Shorebird user
|
||||
patch Publish new patches for a specific release to Shorebird.
|
||||
release Builds and submits your app to Shorebird.
|
||||
run Run the Flutter application.
|
||||
upgrade Upgrade your copy of Shorebird.
|
||||
apps Manage your Shorebird apps.
|
||||
build Build a new release of your application.
|
||||
channels Manage the channels for your Shorebird app.
|
||||
init Initialize Shorebird.
|
||||
login Login as a new Shorebird user.
|
||||
logout Logout of the current Shorebird user
|
||||
patch Publish new patches for a specific release to Shorebird.
|
||||
release Builds and submits your app to Shorebird.
|
||||
run Run the Flutter application.
|
||||
upgrade Upgrade your copy of Shorebird.
|
||||
|
||||
Run "shorebird help <command>" for more information about a command.
|
||||
```
|
||||
|
||||
@@ -48,6 +48,7 @@ class ShorebirdCliCommandRunner extends CompletionCommandRunner<int> {
|
||||
|
||||
addCommand(AppsCommand(logger: _logger));
|
||||
addCommand(BuildCommand(logger: _logger));
|
||||
addCommand(ChannelsCommand(logger: _logger));
|
||||
addCommand(InitCommand(logger: _logger));
|
||||
addCommand(LoginCommand(logger: _logger));
|
||||
addCommand(LogoutCommand(logger: _logger));
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export 'channels_command.dart';
|
||||
export 'list_channels_command.dart';
|
||||
@@ -0,0 +1,19 @@
|
||||
import 'package:shorebird_cli/src/command.dart';
|
||||
import 'package:shorebird_cli/src/commands/commands.dart';
|
||||
|
||||
/// {@template channels_command}
|
||||
/// `shorebird channels`
|
||||
/// Manage the channels for your Shorebird app.
|
||||
/// {@endtemplate}
|
||||
class ChannelsCommand extends ShorebirdCommand {
|
||||
/// {@macro channels_command}
|
||||
ChannelsCommand({required super.logger}) {
|
||||
addSubcommand(ListChannelsCommand(logger: logger));
|
||||
}
|
||||
|
||||
@override
|
||||
String get description => 'Manage the channels for your Shorebird app.';
|
||||
|
||||
@override
|
||||
String get name => 'channels';
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:barbecue/barbecue.dart';
|
||||
import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:shorebird_cli/src/command.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_config_mixin.dart';
|
||||
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
|
||||
|
||||
/// {@template list_channels_command}
|
||||
/// `shorebird channels list`
|
||||
/// List all channels for a Shorebird app.
|
||||
/// {@endtemplate}
|
||||
class ListChannelsCommand extends ShorebirdCommand with ShorebirdConfigMixin {
|
||||
/// {@macro list_channels_command}
|
||||
ListChannelsCommand({
|
||||
required super.logger,
|
||||
super.buildCodePushClient,
|
||||
super.auth,
|
||||
}) {
|
||||
argParser.addOption(
|
||||
_appIdOption,
|
||||
help: 'The app id to list channels for.',
|
||||
);
|
||||
}
|
||||
|
||||
static const String _appIdOption = 'app-id';
|
||||
|
||||
@override
|
||||
String get description => 'List all channels for a Shorebird app.';
|
||||
|
||||
@override
|
||||
String get name => 'list';
|
||||
|
||||
@override
|
||||
List<String> get aliases => ['ls'];
|
||||
|
||||
@override
|
||||
Future<int>? run() async {
|
||||
final session = auth.currentSession;
|
||||
if (session == null) {
|
||||
logger.err('You must be logged in to view channels.');
|
||||
return ExitCode.noUser.code;
|
||||
}
|
||||
|
||||
final client = buildCodePushClient(
|
||||
apiKey: session.apiKey,
|
||||
hostedUri: hostedUri,
|
||||
);
|
||||
|
||||
final appId = results[_appIdOption] as String? ?? getShorebirdYaml()?.appId;
|
||||
if (appId == null) {
|
||||
logger.err(
|
||||
'''
|
||||
Could not find an app id.
|
||||
|
||||
You must either specify an app id via the "--$_appIdOption" flag or run this command from within a directory with a valid "shorebird.yaml" file.''',
|
||||
);
|
||||
return ExitCode.usage.code;
|
||||
}
|
||||
|
||||
final List<Channel> channels;
|
||||
try {
|
||||
channels = await client.getChannels(appId: appId);
|
||||
} catch (error) {
|
||||
logger.err('$error');
|
||||
return ExitCode.software.code;
|
||||
}
|
||||
|
||||
logger.info(
|
||||
'''
|
||||
📱 App ID: ${lightCyan.wrap(appId)}
|
||||
📺 Channels''',
|
||||
);
|
||||
|
||||
if (channels.isEmpty) {
|
||||
logger.info('(empty)');
|
||||
return ExitCode.success.code;
|
||||
}
|
||||
|
||||
logger.info(channels.prettyPrint());
|
||||
|
||||
return ExitCode.success.code;
|
||||
}
|
||||
}
|
||||
|
||||
extension on List<Channel> {
|
||||
String prettyPrint() {
|
||||
const cellStyle = CellStyle(
|
||||
paddingLeft: 1,
|
||||
paddingRight: 1,
|
||||
borderBottom: true,
|
||||
borderTop: true,
|
||||
borderLeft: true,
|
||||
borderRight: true,
|
||||
);
|
||||
return Table(
|
||||
cellStyle: cellStyle,
|
||||
header: const TableSection(
|
||||
rows: [
|
||||
Row(cells: [Cell('Name')])
|
||||
],
|
||||
),
|
||||
body: TableSection(
|
||||
rows: [
|
||||
for (final channel in this) Row(cells: [Cell(channel.name)]),
|
||||
],
|
||||
),
|
||||
).render();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
export 'apps/apps.dart';
|
||||
export 'build_command.dart';
|
||||
export 'channels/channels.dart';
|
||||
export 'init_command.dart';
|
||||
export 'login_command.dart';
|
||||
export 'logout_command.dart';
|
||||
|
||||
@@ -11,6 +11,7 @@ environment:
|
||||
dependencies:
|
||||
archive: ^3.3.6
|
||||
args: ^2.3.1
|
||||
barbecue: ^0.4.0
|
||||
checked_yaml: ^2.0.2
|
||||
cli_completion: ^0.3.0
|
||||
cli_util: ^0.4.0
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
import 'package:args/args.dart';
|
||||
import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:shorebird_cli/src/auth/auth.dart';
|
||||
import 'package:shorebird_cli/src/auth/session.dart';
|
||||
import 'package:shorebird_cli/src/commands/commands.dart';
|
||||
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
class _MockArgResults extends Mock implements ArgResults {}
|
||||
|
||||
class _MockAuth extends Mock implements Auth {}
|
||||
|
||||
class _MockCodePushClient extends Mock implements CodePushClient {}
|
||||
|
||||
class _MockLogger extends Mock implements Logger {}
|
||||
|
||||
void main() {
|
||||
group('list', () {
|
||||
const session = Session(apiKey: 'test-api-key');
|
||||
const appId = 'test-app-id';
|
||||
|
||||
late ArgResults argResults;
|
||||
late Auth auth;
|
||||
late CodePushClient codePushClient;
|
||||
late Logger logger;
|
||||
late ListChannelsCommand command;
|
||||
|
||||
setUp(() {
|
||||
argResults = _MockArgResults();
|
||||
auth = _MockAuth();
|
||||
codePushClient = _MockCodePushClient();
|
||||
logger = _MockLogger();
|
||||
command = ListChannelsCommand(
|
||||
auth: auth,
|
||||
buildCodePushClient: ({required String apiKey, Uri? hostedUri}) {
|
||||
return codePushClient;
|
||||
},
|
||||
logger: logger,
|
||||
)..testArgResults = argResults;
|
||||
|
||||
when(() => argResults['app-id']).thenReturn(appId);
|
||||
when(() => auth.currentSession).thenReturn(session);
|
||||
});
|
||||
|
||||
test('description is correct', () {
|
||||
expect(
|
||||
command.description,
|
||||
equals('List all channels for a Shorebird app.'),
|
||||
);
|
||||
});
|
||||
|
||||
test('returns ExitCode.noUser when not logged in', () async {
|
||||
when(() => auth.currentSession).thenReturn(null);
|
||||
expect(await command.run(), ExitCode.noUser.code);
|
||||
});
|
||||
|
||||
test('returns ExitCode.usage when app id is missing.', () async {
|
||||
when(() => argResults['app-id']).thenReturn(null);
|
||||
expect(await command.run(), ExitCode.usage.code);
|
||||
});
|
||||
|
||||
test('returns ExitCode.software when unable to get channels', () async {
|
||||
when(
|
||||
() => codePushClient.getChannels(appId: any(named: 'appId')),
|
||||
).thenThrow(Exception());
|
||||
expect(await command.run(), ExitCode.software.code);
|
||||
});
|
||||
|
||||
test('returns ExitCode.success when channels are empty', () async {
|
||||
when(
|
||||
() => codePushClient.getChannels(appId: any(named: 'appId')),
|
||||
).thenAnswer((_) async => []);
|
||||
expect(await command.run(), ExitCode.success.code);
|
||||
verify(() => logger.info('(empty)')).called(1);
|
||||
});
|
||||
|
||||
test('returns ExitCode.success when channels are not empty', () async {
|
||||
final channels = [
|
||||
const Channel(id: 0, appId: appId, name: 'stable'),
|
||||
const Channel(id: 1, appId: appId, name: 'development'),
|
||||
];
|
||||
when(
|
||||
() => codePushClient.getChannels(appId: any(named: 'appId')),
|
||||
).thenAnswer((_) async => channels);
|
||||
expect(await command.run(), ExitCode.success.code);
|
||||
verify(
|
||||
() => logger.info(
|
||||
'''
|
||||
📱 App ID: ${lightCyan.wrap(appId)}
|
||||
📺 Channels''',
|
||||
),
|
||||
).called(1);
|
||||
verify(
|
||||
() => logger.info(
|
||||
'''
|
||||
┌─────────────┐
|
||||
│ Name │
|
||||
├─────────────┤
|
||||
│ stable │
|
||||
├─────────────┤
|
||||
│ development │
|
||||
└─────────────┘''',
|
||||
),
|
||||
).called(1);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user