feat(shorebird_cli): shorebird channels create (#212)
This commit is contained in:
@@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user