feat(shorebird_cli): shorebird channels create (#212)

This commit is contained in:
Felix Angelov
2023-03-31 15:03:39 -05:00
committed by GitHub
parent 0be5e247d9
commit 92da9d0ddd
5 changed files with 228 additions and 0 deletions
+24
View File
@@ -268,6 +268,30 @@ shorebird channels list
└─────────────┘
```
### Create Channels
Create a new channel for your application using the `shorebird channels create` command:
```bash
shorebird channels create --name MyChannel
```
**Sample**
```
shorebird channels create --name MyChannel
🚀 Ready to create a new channel!
📱 App ID: 485df03f-f522-4242-bf3d-31c0869bacac
📺 Channel: MyChannel
Would you like to continue? (y/N) Yes
✓ Creating channel (0.2s)
✅ New Channel Created!
```
### Usage
```
@@ -1,2 +1,3 @@
export 'channels_command.dart';
export 'create_channels_command.dart';
export 'list_channels_command.dart';
@@ -8,6 +8,7 @@ import 'package:shorebird_cli/src/commands/commands.dart';
class ChannelsCommand extends ShorebirdCommand {
/// {@macro channels_command}
ChannelsCommand({required super.logger}) {
addSubcommand(CreateChannelsCommand(logger: logger));
addSubcommand(ListChannelsCommand(logger: logger));
}
@@ -0,0 +1,95 @@
import 'dart:async';
import 'package:mason_logger/mason_logger.dart';
import 'package:shorebird_cli/src/command.dart';
import 'package:shorebird_cli/src/shorebird_config_mixin.dart';
/// {@template create_channels_command}
/// `shorebird channels create`
/// Create a new channel for a Shorebird app.
/// {@endtemplate}
class CreateChannelsCommand extends ShorebirdCommand with ShorebirdConfigMixin {
/// {@macro create_channels_command}
CreateChannelsCommand({
required super.logger,
super.buildCodePushClient,
super.auth,
}) {
argParser
..addOption(
_appIdOption,
help: 'The app id to create a channel for.',
)
..addOption(
_channelNameOption,
help: 'The name of the channel to create.',
);
}
static const String _appIdOption = 'app-id';
static const String _channelNameOption = 'name';
@override
String get description => 'Create a new channel for a Shorebird app.';
@override
String get name => 'create';
@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 channel = results[_channelNameOption] as String;
logger.info(
'''
${styleBold.wrap(lightGreen.wrap('🚀 Ready to create a new channel!'))}
📱 App ID: ${lightCyan.wrap(appId)}
📺 Channel: ${lightCyan.wrap(channel)}
''',
);
final confirm = logger.confirm('Would you like to continue?');
if (!confirm) {
logger.info('Aborted.');
return ExitCode.success.code;
}
final progress = logger.progress('Creating channel');
try {
await client.createChannel(appId: appId, channel: channel);
progress.complete();
} catch (error) {
progress.fail();
logger.err('$error');
return ExitCode.software.code;
}
logger.success('\n✅ New Channel Created!');
return ExitCode.success.code;
}
}
@@ -0,0 +1,107 @@
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 {}
class _MockProgress extends Mock implements Progress {}
void main() {
group('create', () {
const session = Session(apiKey: 'test-api-key');
const appId = 'test-app-id';
const channelName = 'my-channel';
const channel = Channel(id: 0, appId: appId, name: channelName);
late ArgResults argResults;
late Auth auth;
late CodePushClient codePushClient;
late Logger logger;
late Progress progress;
late CreateChannelsCommand command;
setUp(() {
argResults = _MockArgResults();
auth = _MockAuth();
codePushClient = _MockCodePushClient();
logger = _MockLogger();
progress = _MockProgress();
command = CreateChannelsCommand(
auth: auth,
buildCodePushClient: ({required String apiKey, Uri? hostedUri}) {
return codePushClient;
},
logger: logger,
)..testArgResults = argResults;
when(() => argResults['app-id']).thenReturn(appId);
when(() => argResults['name']).thenReturn(channelName);
when(() => auth.currentSession).thenReturn(session);
when(() => logger.confirm(any())).thenReturn(true);
when(() => logger.progress(any())).thenReturn(progress);
});
test('description is correct', () {
expect(
command.description,
equals('Create a new channel 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.success when user aborts', () async {
when(() => logger.confirm(any())).thenReturn(false);
expect(await command.run(), ExitCode.success.code);
verifyNever(
() => codePushClient.createChannel(
appId: any(named: 'appId'),
channel: any(named: 'channel'),
),
);
verify(() => logger.info('Aborted.')).called(1);
});
test('returns ExitCode.software when creating a channel fails', () async {
const error = 'oops something went wrong';
when(
() => codePushClient.createChannel(
appId: any(named: 'appId'),
channel: any(named: 'channel'),
),
).thenThrow(error);
expect(await command.run(), ExitCode.software.code);
verify(() => logger.err(error)).called(1);
});
test('returns ExitCode.success on success', () async {
when(
() => codePushClient.createChannel(
appId: any(named: 'appId'),
channel: any(named: 'channel'),
),
).thenAnswer((_) async => channel);
expect(await command.run(), ExitCode.success.code);
verify(() => logger.success('\n✅ New Channel Created!')).called(1);
});
});
}