chore: create new cli

This commit is contained in:
Felix Angelov
2023-03-02 11:37:56 -06:00
commit 0b8293f2aa
25 changed files with 1043 additions and 0 deletions
+9
View File
@@ -0,0 +1,9 @@
@Tags(['version-verify'])
library;
import 'package:build_verify/build_verify.dart';
import 'package:test/test.dart';
void main() {
test('ensure_build', expectBuildClean);
}
+178
View File
@@ -0,0 +1,178 @@
import 'dart:io';
import 'package:args/command_runner.dart';
import 'package:cli_completion/cli_completion.dart';
import 'package:mason_logger/mason_logger.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pub_updater/pub_updater.dart';
import 'package:shorebird_cli/src/command_runner.dart';
import 'package:shorebird_cli/src/version.dart';
import 'package:test/test.dart';
class _MockLogger extends Mock implements Logger {}
class _MockProcessResult extends Mock implements ProcessResult {}
class _MockProgress extends Mock implements Progress {}
class _MockPubUpdater extends Mock implements PubUpdater {}
const latestVersion = '0.0.0';
final updatePrompt = '''
${lightYellow.wrap('Update available!')} ${lightCyan.wrap(packageVersion)} \u2192 ${lightCyan.wrap(latestVersion)}
Run ${lightCyan.wrap('$executableName update')} to update''';
void main() {
group('ShorebirdCliCommandRunner', () {
late PubUpdater pubUpdater;
late Logger logger;
late ProcessResult processResult;
late ShorebirdCliCommandRunner commandRunner;
setUp(() {
pubUpdater = _MockPubUpdater();
when(
() => pubUpdater.getLatestVersion(any()),
).thenAnswer((_) async => packageVersion);
logger = _MockLogger();
processResult = _MockProcessResult();
when(() => processResult.exitCode).thenReturn(ExitCode.success.code);
commandRunner = ShorebirdCliCommandRunner(
logger: logger,
pubUpdater: pubUpdater,
);
});
test('shows update message when newer version exists', () async {
when(
() => pubUpdater.getLatestVersion(any()),
).thenAnswer((_) async => latestVersion);
final result = await commandRunner.run(['--version']);
expect(result, equals(ExitCode.success.code));
verify(() => logger.info(updatePrompt)).called(1);
});
test(
'Does not show update message when the shell calls the '
'completion command',
() async {
when(
() => pubUpdater.getLatestVersion(any()),
).thenAnswer((_) async => latestVersion);
final result = await commandRunner.run(['completion']);
expect(result, equals(ExitCode.success.code));
verifyNever(() => logger.info(updatePrompt));
},
);
test('does not show update message when using update command', () async {
when(
() => pubUpdater.getLatestVersion(any()),
).thenAnswer((_) async => latestVersion);
when(
() => pubUpdater.update(
packageName: packageName,
versionConstraint: any(named: 'versionConstraint'),
),
).thenAnswer((_) async => processResult);
when(
() => pubUpdater.isUpToDate(
packageName: any(named: 'packageName'),
currentVersion: any(named: 'currentVersion'),
),
).thenAnswer((_) async => true);
final progress = _MockProgress();
final progressLogs = <String>[];
when(() => progress.complete(any())).thenAnswer((_) {
final message = _.positionalArguments.elementAt(0) as String?;
if (message != null) progressLogs.add(message);
});
when(() => logger.progress(any())).thenReturn(progress);
final result = await commandRunner.run(['update']);
expect(result, equals(ExitCode.success.code));
verifyNever(() => logger.info(updatePrompt));
});
test('can be instantiated without an explicit analytics/logger instance',
() {
final commandRunner = ShorebirdCliCommandRunner();
expect(commandRunner, isNotNull);
expect(commandRunner, isA<CompletionCommandRunner<int>>());
});
test('handles FormatException', () async {
const exception = FormatException('oops!');
var isFirstInvocation = true;
when(() => logger.info(any())).thenAnswer((_) {
if (isFirstInvocation) {
isFirstInvocation = false;
throw exception;
}
});
final result = await commandRunner.run(['--version']);
expect(result, equals(ExitCode.usage.code));
verify(() => logger.err(exception.message)).called(1);
verify(() => logger.info(commandRunner.usage)).called(1);
});
test('handles UsageException', () async {
final exception = UsageException('oops!', 'exception usage');
var isFirstInvocation = true;
when(() => logger.info(any())).thenAnswer((_) {
if (isFirstInvocation) {
isFirstInvocation = false;
throw exception;
}
});
final result = await commandRunner.run(['--version']);
expect(result, equals(ExitCode.usage.code));
verify(() => logger.err(exception.message)).called(1);
verify(() => logger.info('exception usage')).called(1);
});
group('--version', () {
test('outputs current version', () async {
final result = await commandRunner.run(['--version']);
expect(result, equals(ExitCode.success.code));
verify(() => logger.info(packageVersion)).called(1);
});
});
group('--verbose', () {
test('enables verbose logging', () async {
final result = await commandRunner.run(['--verbose']);
expect(result, equals(ExitCode.success.code));
verify(() => logger.detail('Argument information:')).called(1);
verify(() => logger.detail(' Top level options:')).called(1);
verify(() => logger.detail(' - verbose: true')).called(1);
verifyNever(() => logger.detail(' Command options:'));
});
test('enables verbose logging for sub commands', () async {
final result = await commandRunner.run([
'--verbose',
'sample',
'--cyan',
]);
expect(result, equals(ExitCode.success.code));
verify(() => logger.detail('Argument information:')).called(1);
verify(() => logger.detail(' Top level options:')).called(1);
verify(() => logger.detail(' - verbose: true')).called(1);
verify(() => logger.detail(' Command: sample')).called(1);
verify(() => logger.detail(' Command options:')).called(1);
verify(() => logger.detail(' - cyan: true')).called(1);
});
});
});
}
@@ -0,0 +1,26 @@
import 'package:mason_logger/mason_logger.dart';
import 'package:mocktail/mocktail.dart';
import 'package:shorebird_cli/src/command_runner.dart';
import 'package:test/test.dart';
class _MockLogger extends Mock implements Logger {}
void main() {
group('publish', () {
late Logger logger;
late ShorebirdCliCommandRunner commandRunner;
setUp(() {
logger = _MockLogger();
commandRunner = ShorebirdCliCommandRunner(logger: logger);
});
test('outputs coming soon...', () async {
final exitCode = await commandRunner.run(['publish']);
expect(exitCode, ExitCode.success.code);
verify(() => logger.info('Coming soon...')).called(1);
});
});
}
+188
View File
@@ -0,0 +1,188 @@
import 'dart:io';
import 'package:mason_logger/mason_logger.dart';
import 'package:mocktail/mocktail.dart';
import 'package:pub_updater/pub_updater.dart';
import 'package:shorebird_cli/src/command_runner.dart';
import 'package:shorebird_cli/src/commands/commands.dart';
import 'package:shorebird_cli/src/version.dart';
import 'package:test/test.dart';
class _MockLogger extends Mock implements Logger {}
class _MockProcessResult extends Mock implements ProcessResult {}
class _MockProgress extends Mock implements Progress {}
class _MockPubUpdater extends Mock implements PubUpdater {}
void main() {
const latestVersion = '0.0.0';
group('update', () {
late PubUpdater pubUpdater;
late Logger logger;
late ProcessResult processResult;
late ShorebirdCliCommandRunner commandRunner;
setUp(() {
final progress = _MockProgress();
final progressLogs = <String>[];
pubUpdater = _MockPubUpdater();
logger = _MockLogger();
processResult = _MockProcessResult();
commandRunner = ShorebirdCliCommandRunner(
logger: logger,
pubUpdater: pubUpdater,
);
when(
() => pubUpdater.getLatestVersion(any()),
).thenAnswer((_) async => packageVersion);
when(
() => pubUpdater.update(
packageName: packageName,
versionConstraint: latestVersion,
),
).thenAnswer((_) async => processResult);
when(
() => pubUpdater.isUpToDate(
packageName: any(named: 'packageName'),
currentVersion: any(named: 'currentVersion'),
),
).thenAnswer((_) async => true);
when(() => progress.complete(any())).thenAnswer((_) {
final message = _.positionalArguments.elementAt(0) as String?;
if (message != null) progressLogs.add(message);
});
when(() => logger.progress(any())).thenReturn(progress);
when(() => processResult.exitCode).thenReturn(ExitCode.success.code);
});
test('can be instantiated without a pub updater', () {
final command = UpdateCommand(logger: logger);
expect(command, isNotNull);
});
test(
'handles pub latest version query errors',
() async {
when(
() => pubUpdater.getLatestVersion(any()),
).thenThrow(Exception('oops'));
final result = await commandRunner.run(['update']);
expect(result, equals(ExitCode.software.code));
verify(() => logger.progress('Checking for updates')).called(1);
verify(() => logger.err('Exception: oops'));
verifyNever(
() => pubUpdater.update(
packageName: any(named: 'packageName'),
versionConstraint: any(named: 'versionConstraint'),
),
);
},
);
test(
'handles pub update errors',
() async {
when(
() => pubUpdater.getLatestVersion(any()),
).thenAnswer((_) async => latestVersion);
when(
() => pubUpdater.update(
packageName: any(named: 'packageName'),
versionConstraint: any(named: 'versionConstraint'),
),
).thenThrow(Exception('oops'));
final result = await commandRunner.run(['update']);
expect(result, equals(ExitCode.software.code));
verify(() => logger.progress('Checking for updates')).called(1);
verify(() => logger.err('Exception: oops'));
verify(
() => pubUpdater.update(
packageName: any(named: 'packageName'),
versionConstraint: any(named: 'versionConstraint'),
),
).called(1);
},
);
test('handles pub update process errors', () async {
const error = 'Oh no! Installing this is not possible right now!';
when(() => processResult.exitCode).thenReturn(1);
when<dynamic>(() => processResult.stderr).thenReturn(error);
when(
() => pubUpdater.getLatestVersion(any()),
).thenAnswer((_) async => latestVersion);
when(
() => pubUpdater.update(
packageName: any(named: 'packageName'),
versionConstraint: any(named: 'versionConstraint'),
),
).thenAnswer((_) async => processResult);
final result = await commandRunner.run(['update']);
expect(result, equals(ExitCode.software.code));
verify(() => logger.progress('Checking for updates')).called(1);
verify(() => logger.err('Error updating CLI: $error'));
verify(
() => pubUpdater.update(
packageName: any(named: 'packageName'),
versionConstraint: any(named: 'versionConstraint'),
),
).called(1);
});
test(
'updates when newer version exists',
() async {
when(
() => pubUpdater.getLatestVersion(any()),
).thenAnswer((_) async => latestVersion);
when(
() => pubUpdater.update(
packageName: any(named: 'packageName'),
versionConstraint: any(named: 'versionConstraint'),
),
).thenAnswer((_) async => processResult);
when(() => logger.progress(any())).thenReturn(_MockProgress());
final result = await commandRunner.run(['update']);
expect(result, equals(ExitCode.success.code));
verify(() => logger.progress('Checking for updates')).called(1);
verify(() => logger.progress('Updating to $latestVersion')).called(1);
verify(
() => pubUpdater.update(
packageName: packageName,
versionConstraint: latestVersion,
),
).called(1);
},
);
test(
'does not update when already on latest version',
() async {
when(
() => pubUpdater.getLatestVersion(any()),
).thenAnswer((_) async => packageVersion);
when(() => logger.progress(any())).thenReturn(_MockProgress());
final result = await commandRunner.run(['update']);
expect(result, equals(ExitCode.success.code));
verify(
() => logger.info('CLI is already at the latest version.'),
).called(1);
verifyNever(() => logger.progress('Updating to $latestVersion'));
verifyNever(
() => pubUpdater.update(
packageName: any(named: 'packageName'),
versionConstraint: any(named: 'versionConstraint'),
),
);
},
);
});
}