fix: delete shorebird build commands (#2453)

This commit is contained in:
Bryan Oltman
2024-08-28 10:51:41 -04:00
committed by GitHub
parent 5269a288fa
commit 1085170d60
13 changed files with 0 additions and 1254 deletions
@@ -1,5 +0,0 @@
export 'build_aar_command.dart';
export 'build_apk_command.dart';
export 'build_app_bundle_command.dart';
export 'build_command.dart';
export 'build_ipa_command.dart';
@@ -1,87 +0,0 @@
// ignore_for_file: public_member_api_docs
import 'dart:async';
import 'package:mason_logger/mason_logger.dart';
import 'package:path/path.dart' as p;
import 'package:shorebird_cli/src/artifact_builder.dart';
import 'package:shorebird_cli/src/extensions/arg_results.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_command.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
/// {@template build_aar_command}
///
/// `shorebird build aar`
/// Build an Android aar file from your app.
/// {@endtemplate}
class BuildAarCommand extends ShorebirdCommand {
BuildAarCommand() {
// We would have a "target" option here, similar to what [BuildApkCommand]
// and [BuildAabCommand] have, but target cannot currently be configured in
// `flutter build aar` and is always assumed to be lib/main.dart.
argParser
// `flutter build aar` defaults to a build number of 1.0, so we do the
// same.
.addOption(
'build-number',
help: 'The build number of the aar',
defaultsTo: '1.0',
);
}
@override
String get name => 'aar';
@override
String get description => 'Build an Android AAR file from your module.';
@override
Future<int> run() async {
try {
await shorebirdValidator.validatePreconditions(
checkUserIsAuthenticated: true,
checkShorebirdInitialized: true,
);
} on PreconditionFailedException catch (e) {
return e.exitCode.code;
}
if (shorebirdEnv.androidPackageName == null) {
logger.err('Could not find androidPackage in pubspec.yaml.');
return ExitCode.config.code;
}
final buildNumber = results['build-number'] as String;
final buildProgress = logger.progress('Building aar');
try {
await artifactBuilder.buildAar(
buildNumber: buildNumber,
args: results.forwardedArgs,
);
} on ArtifactBuildException catch (error) {
buildProgress.fail('Failed to build: ${error.message}');
return ExitCode.software.code;
}
buildProgress.complete();
final aarPath = p.joinAll([
'build',
'host',
'outputs',
'repo',
...shorebirdEnv.androidPackageName!.split('.'),
'flutter_release',
buildNumber,
'flutter_release-$buildNumber.aar',
]);
logger.info('''
📦 Generated an aar at:
${lightCyan.wrap(aarPath)}''');
return ExitCode.success.code;
}
}
@@ -1,75 +0,0 @@
import 'package:mason_logger/mason_logger.dart';
import 'package:path/path.dart' as p;
import 'package:shorebird_cli/src/artifact_builder.dart';
import 'package:shorebird_cli/src/doctor.dart';
import 'package:shorebird_cli/src/extensions/arg_results.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_command.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
/// {@template build_apk_command}
///
/// `shorebird build apk`
/// Build an Android APK file from your app.
/// {@endtemplate}
class BuildApkCommand extends ShorebirdCommand {
/// {@macro build_apk_command}
BuildApkCommand() {
argParser
..addOption(
'target',
abbr: 't',
help: 'The main entrypoint file of the application.',
)
..addOption(
'flavor',
help: 'The product flavor to use when building the app.',
);
}
@override
String get description => 'Build an Android APK file from your app.';
@override
String get name => 'apk';
@override
Future<int> run() async {
try {
await shorebirdValidator.validatePreconditions(
checkUserIsAuthenticated: true,
checkShorebirdInitialized: true,
validators: doctor.androidCommandValidators,
);
} on PreconditionFailedException catch (e) {
return e.exitCode.code;
}
final flavor = results['flavor'] as String?;
final target = results['target'] as String?;
final buildProgress = logger.progress('Building apk');
try {
await artifactBuilder.buildApk(
flavor: flavor,
target: target,
args: results.forwardedArgs,
);
} on ArtifactBuildException catch (error) {
buildProgress.fail(error.message);
return ExitCode.software.code;
}
buildProgress.complete();
final apkDirPath = p.join('build', 'app', 'outputs', 'apk');
final apkPath = flavor != null
? p.join(apkDirPath, flavor, 'release', 'app-$flavor-release.apk')
: p.join(apkDirPath, 'release', 'app-release.apk');
logger.info('''
📦 Generated an apk at:
${lightCyan.wrap(apkPath)}''');
return ExitCode.success.code;
}
}
@@ -1,74 +0,0 @@
import 'package:mason_logger/mason_logger.dart';
import 'package:path/path.dart' as p;
import 'package:shorebird_cli/src/artifact_builder.dart';
import 'package:shorebird_cli/src/doctor.dart';
import 'package:shorebird_cli/src/extensions/arg_results.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_command.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
/// {@template build_app_bundle_command}
///
/// `shorebird build appbundle`
/// Build an Android App Bundle file from your app.
/// {@endtemplate}
class BuildAppBundleCommand extends ShorebirdCommand {
/// {@macro build_app_bundle_command}
BuildAppBundleCommand() {
argParser
..addOption(
'target',
abbr: 't',
help: 'The main entrypoint file of the application.',
)
..addOption(
'flavor',
help: 'The product flavor to use when building the app.',
);
}
@override
String get description => 'Build an Android App Bundle file from your app.';
@override
String get name => 'appbundle';
@override
Future<int> run() async {
try {
await shorebirdValidator.validatePreconditions(
checkUserIsAuthenticated: true,
checkShorebirdInitialized: true,
validators: doctor.androidCommandValidators,
);
} on PreconditionFailedException catch (e) {
return e.exitCode.code;
}
final flavor = results['flavor'] as String?;
final target = results['target'] as String?;
final buildProgress = logger.progress('Building appbundle');
try {
await artifactBuilder.buildAppBundle(
flavor: flavor,
target: target,
args: results.forwardedArgs,
);
} on ArtifactBuildException catch (error) {
buildProgress.fail(error.message);
return ExitCode.software.code;
}
final bundleDirPath = p.join('build', 'app', 'outputs', 'bundle');
final bundlePath = flavor != null
? p.join(bundleDirPath, '${flavor}Release', 'app-$flavor-release.aab')
: p.join(bundleDirPath, 'release', 'app-release.aab');
buildProgress.complete();
logger.info('''
📦 Generated an app bundle at:
${lightCyan.wrap(bundlePath)}''');
return ExitCode.success.code;
}
}
@@ -1,25 +0,0 @@
import 'package:shorebird_cli/src/commands/build/build.dart';
import 'package:shorebird_cli/src/shorebird_command.dart';
/// {@template build_command}
/// `shorebird build`
/// Build a new release of your application.
/// {@endtemplate}
class BuildCommand extends ShorebirdCommand {
/// {@macro build_command}
BuildCommand() {
addSubcommand(BuildAarCommand());
addSubcommand(BuildApkCommand());
addSubcommand(BuildAppBundleCommand());
addSubcommand(BuildIpaCommand());
}
@override
String get description => 'Build a new release of your application.';
@override
String get name => 'build';
@override
bool get hidden => true;
}
@@ -1,99 +0,0 @@
import 'package:mason_logger/mason_logger.dart';
import 'package:path/path.dart' as p;
import 'package:shorebird_cli/src/artifact_builder.dart';
import 'package:shorebird_cli/src/doctor.dart';
import 'package:shorebird_cli/src/extensions/arg_results.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_command.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
/// {@template build_ipa_command}
/// `shorebird build ipa`
/// Builds an .xcarchive and optionally .ipa for an iOS app to be generated for
/// App Store submission.
/// {@endtemplate}
class BuildIpaCommand extends ShorebirdCommand {
/// {@macro build_ipa_command}
BuildIpaCommand() {
argParser
..addOption(
'target',
abbr: 't',
help: 'The main entrypoint file of the application.',
)
..addOption(
'flavor',
help: 'The product flavor to use when building the app.',
)
..addFlag(
'codesign',
help:
'''Codesign the application bundle (only available on device builds).''',
);
}
@override
String get description =>
'''Builds an .xcarchive and optionally .ipa for an iOS app to be generated for App Store submission.''';
@override
String get name => 'ipa';
@override
Future<int> run() async {
try {
await shorebirdValidator.validatePreconditions(
checkUserIsAuthenticated: true,
checkShorebirdInitialized: true,
validators: doctor.iosCommandValidators,
);
} on PreconditionFailedException catch (e) {
return e.exitCode.code;
}
final flavor = results['flavor'] as String?;
final target = results['target'] as String?;
final codesign = results['codesign'] as bool;
if (!codesign) {
logger.warn('''
Codesigning is disabled. You must manually codesign before deploying to devices.''');
}
final buildProgress = logger.progress('Building ipa');
try {
await artifactBuilder.buildIpa(
flavor: flavor,
target: target,
codesign: codesign,
args: results.forwardedArgs,
);
} on ArtifactBuildException catch (error) {
buildProgress.fail('Failed to build: ${error.message}');
return ExitCode.software.code;
}
buildProgress.complete();
final xcarchivePath = p.join('build', 'ios', 'archive', 'Runner.xcarchive');
logger.info('''
📦 Generated an xcode archive at:
${lightCyan.wrap(xcarchivePath)}''');
if (!codesign) {
logger.info(
'Codesigning disabled via "--no-codesign". Skipping ipa generation.',
);
return ExitCode.success.code;
}
final ipaPath = p.join('build', 'ios', 'ipa', 'Runner.ipa');
logger.info('''
📦 Generated an ipa at:
${lightCyan.wrap(ipaPath)}''');
return ExitCode.success.code;
}
}
@@ -1,4 +1,3 @@
export 'build/build_command.dart';
export 'cache/cache.dart';
export 'doctor_command.dart';
export 'flutter/flutter.dart';
@@ -67,7 +67,6 @@ class ShorebirdCliCommandRunner extends CompletionCommandRunner<int> {
help: 'The build of the local engine to use as the host platform.',
);
addCommand(BuildCommand());
addCommand(CacheCommand());
addCommand(DoctorCommand());
addCommand(FlutterCommand());
@@ -1,190 +0,0 @@
import 'package:args/args.dart';
import 'package:mason_logger/mason_logger.dart';
import 'package:mocktail/mocktail.dart';
import 'package:path/path.dart' as p;
import 'package:scoped_deps/scoped_deps.dart';
import 'package:shorebird_cli/src/artifact_builder.dart';
import 'package:shorebird_cli/src/commands/build/build.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:test/test.dart';
import '../../mocks.dart';
void main() {
group(BuildAarCommand, () {
const buildNumber = '1.0';
const androidPackageName = 'com.example.my_flutter_module';
late ArgResults argResults;
late ArtifactBuilder artifactBuilder;
late ShorebirdLogger logger;
late Progress progress;
late ShorebirdEnv shorebirdEnv;
late ShorebirdValidator shorebirdValidator;
late BuildAarCommand command;
R runWithOverrides<R>(R Function() body) {
return runScoped(
body,
values: {
artifactBuilderRef.overrideWith(() => artifactBuilder),
loggerRef.overrideWith(() => logger),
shorebirdEnvRef.overrideWith(() => shorebirdEnv),
shorebirdValidatorRef.overrideWith(() => shorebirdValidator),
},
);
}
setUp(() {
argResults = MockArgResults();
artifactBuilder = MockArtifactBuilder();
logger = MockShorebirdLogger();
progress = MockProgress();
shorebirdEnv = MockShorebirdEnv();
shorebirdValidator = MockShorebirdValidator();
when(() => argResults['build-number']).thenReturn(buildNumber);
when(() => argResults.rest).thenReturn([]);
when(() => argResults.wasParsed(any())).thenReturn(false);
when(() => logger.progress(any())).thenReturn(progress);
when(
() => artifactBuilder.buildAar(
buildNumber: any(named: 'buildNumber'),
args: any(named: 'args'),
),
).thenAnswer((_) async => {});
when(
() => shorebirdEnv.androidPackageName,
).thenReturn(androidPackageName);
when(
() => shorebirdValidator.validatePreconditions(
checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'),
checkShorebirdInitialized: any(named: 'checkShorebirdInitialized'),
),
).thenAnswer((_) async {});
command = runWithOverrides(BuildAarCommand.new)
..testArgResults = argResults;
});
test('has a description', () {
expect(command.description, isNotEmpty);
});
test('exits when validation fails', () async {
final exception = ValidationFailedException();
when(
() => shorebirdValidator.validatePreconditions(
checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'),
checkShorebirdInitialized: any(named: 'checkShorebirdInitialized'),
),
).thenThrow(exception);
await expectLater(
runWithOverrides(command.run),
completion(equals(exception.exitCode.code)),
);
verify(
() => shorebirdValidator.validatePreconditions(
checkUserIsAuthenticated: true,
checkShorebirdInitialized: true,
),
).called(1);
});
test('exits with 78 if no module entry exists in pubspec.yaml', () async {
when(() => shorebirdEnv.androidPackageName).thenReturn(null);
final result = await runWithOverrides(command.run);
expect(result, ExitCode.config.code);
});
test('exits with code 70 when building aar fails', () async {
when(
() => artifactBuilder.buildAar(
buildNumber: any(named: 'buildNumber'),
args: any(named: 'args'),
),
).thenThrow(ArtifactBuildException('Failed to build: error'));
final result = await runWithOverrides(command.run);
expect(result, equals(ExitCode.software.code));
verify(
() => artifactBuilder.buildAar(buildNumber: buildNumber, args: []),
).called(1);
verify(
() => progress.fail(any(that: contains('Failed to build'))),
).called(1);
});
group('when platform was specified via arg results rest', () {
setUp(() {
when(() => argResults.rest).thenReturn(['android', '--verbose']);
});
test('exits with code 0 when building aar succeeds', () async {
final result = await runWithOverrides(command.run);
expect(result, equals(ExitCode.success.code));
verify(
() => artifactBuilder.buildAar(
buildNumber: buildNumber,
args: ['--verbose'],
),
).called(1);
verify(
() => logger.info(
'''
📦 Generated an aar at:
${lightCyan.wrap(
p.join(
'build',
'host',
'outputs',
'repo',
'com',
'example',
'my_flutter_module',
'flutter_release',
buildNumber,
'flutter_release-$buildNumber.aar',
),
)}''',
),
).called(1);
});
});
test('exits with code 0 when building aar succeeds', () async {
final result = await runWithOverrides(command.run);
expect(result, equals(ExitCode.success.code));
verify(
() => artifactBuilder.buildAar(buildNumber: buildNumber, args: []),
).called(1);
verify(
() => logger.info(
'''
📦 Generated an aar at:
${lightCyan.wrap(
p.join(
'build',
'host',
'outputs',
'repo',
'com',
'example',
'my_flutter_module',
'flutter_release',
buildNumber,
'flutter_release-$buildNumber.aar',
),
)}''',
),
).called(1);
});
});
}
@@ -1,191 +0,0 @@
import 'dart:io';
import 'package:args/args.dart';
import 'package:mason_logger/mason_logger.dart';
import 'package:mocktail/mocktail.dart';
import 'package:path/path.dart' as p;
import 'package:scoped_deps/scoped_deps.dart';
import 'package:shorebird_cli/src/artifact_builder.dart';
import 'package:shorebird_cli/src/commands/build/build.dart';
import 'package:shorebird_cli/src/doctor.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_cli/src/validators/validators.dart';
import 'package:test/test.dart';
import '../../fakes.dart';
import '../../mocks.dart';
void main() {
group(BuildApkCommand, () {
late ArgResults argResults;
late ArtifactBuilder artifactBuilder;
late Doctor doctor;
late ShorebirdLogger logger;
late ShorebirdFlutterValidator flutterValidator;
late ShorebirdValidator shorebirdValidator;
late BuildApkCommand command;
R runWithOverrides<R>(R Function() body) {
return runScoped(
body,
values: {
artifactBuilderRef.overrideWith(() => artifactBuilder),
doctorRef.overrideWith(() => doctor),
loggerRef.overrideWith(() => logger),
shorebirdValidatorRef.overrideWith(() => shorebirdValidator),
},
);
}
setUpAll(() {
registerFallbackValue(Directory(''));
registerFallbackValue(FakeShorebirdProcess());
});
setUp(() {
argResults = MockArgResults();
artifactBuilder = MockArtifactBuilder();
doctor = MockDoctor();
logger = MockShorebirdLogger();
flutterValidator = MockShorebirdFlutterValidator();
shorebirdValidator = MockShorebirdValidator();
when(() => argResults.rest).thenReturn([]);
when(() => argResults.wasParsed(any())).thenReturn(false);
when(() => logger.progress(any())).thenReturn(MockProgress());
when(() => logger.info(any())).thenReturn(null);
when(
() => shorebirdValidator.validatePreconditions(
checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'),
checkShorebirdInitialized: any(named: 'checkShorebirdInitialized'),
validators: any(named: 'validators'),
),
).thenAnswer((_) async {});
when(
() => doctor.androidCommandValidators,
).thenReturn([flutterValidator]);
when(
() => artifactBuilder.buildApk(
flavor: any(named: 'flavor'),
target: any(named: 'target'),
args: any(named: 'args'),
),
).thenAnswer((_) async => File(''));
command = runWithOverrides(BuildApkCommand.new)
..testArgResults = argResults;
});
test('has a description', () {
expect(command.description, isNotEmpty);
});
test('exits when validation fails', () async {
final exception = ValidationFailedException();
when(
() => shorebirdValidator.validatePreconditions(
checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'),
checkShorebirdInitialized: any(named: 'checkShorebirdInitialized'),
validators: any(named: 'validators'),
),
).thenThrow(exception);
await expectLater(
runWithOverrides(command.run),
completion(equals(exception.exitCode.code)),
);
verify(
() => shorebirdValidator.validatePreconditions(
checkUserIsAuthenticated: true,
checkShorebirdInitialized: true,
validators: [flutterValidator],
),
).called(1);
});
test('exits with code 70 when building apk fails', () async {
when(
() => artifactBuilder.buildApk(
flavor: any(named: 'flavor'),
target: any(named: 'target'),
args: any(named: 'args'),
),
).thenThrow(ArtifactBuildException('oops'));
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.software.code));
verify(
() => artifactBuilder.buildApk(args: []),
).called(1);
});
group('when platform was specified via arg results rest', () {
setUp(() {
when(() => argResults.rest).thenReturn(['android', '--verbose']);
});
test('exits with code 0 when building apk succeeds', () async {
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.success.code));
verify(
() => artifactBuilder.buildApk(args: ['--verbose']),
).called(1);
verify(
() => logger.info(
'''
📦 Generated an apk at:
${lightCyan.wrap(p.join('build', 'app', 'outputs', 'apk', 'release', 'app-release.apk'))}''',
),
).called(1);
});
});
test('exits with code 0 when building apk succeeds', () async {
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.success.code));
verify(
() => artifactBuilder.buildApk(args: []),
).called(1);
verify(
() => logger.info(
'''
📦 Generated an apk at:
${lightCyan.wrap(p.join('build', 'app', 'outputs', 'apk', 'release', 'app-release.apk'))}''',
),
).called(1);
});
test(
'exits with code 0 when building apk succeeds '
'with flavor and target', () async {
const flavor = 'development';
final target = p.join('lib', 'main_development.dart');
when(() => argResults['flavor']).thenReturn(flavor);
when(() => argResults['target']).thenReturn(target);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.success.code));
verify(
() => artifactBuilder.buildApk(
flavor: flavor,
target: target,
args: [],
),
).called(1);
verify(
() => logger.info(
'''
📦 Generated an apk at:
${lightCyan.wrap(p.join('build', 'app', 'outputs', 'apk', flavor, 'release', 'app-$flavor-release.apk'))}''',
),
).called(1);
});
});
}
@@ -1,237 +0,0 @@
import 'dart:io';
import 'package:args/args.dart';
import 'package:mason_logger/mason_logger.dart';
import 'package:mocktail/mocktail.dart';
import 'package:path/path.dart' as p;
import 'package:scoped_deps/scoped_deps.dart';
import 'package:shorebird_cli/src/artifact_builder.dart';
import 'package:shorebird_cli/src/commands/build/build.dart';
import 'package:shorebird_cli/src/doctor.dart';
import 'package:shorebird_cli/src/engine_config.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/platform/platform.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_cli/src/validators/validators.dart';
import 'package:test/test.dart';
import '../../fakes.dart';
import '../../mocks.dart';
void main() {
group(BuildAppBundleCommand, () {
late ArgResults argResults;
late ArtifactBuilder artifactBuilder;
late Doctor doctor;
late ShorebirdLogger logger;
late BuildAppBundleCommand command;
late ShorebirdFlutterValidator flutterValidator;
late ShorebirdValidator shorebirdValidator;
R runWithOverrides<R>(R Function() body) {
return runScoped(
body,
values: {
artifactBuilderRef.overrideWith(() => artifactBuilder),
doctorRef.overrideWith(() => doctor),
engineConfigRef.overrideWith(() => const EngineConfig.empty()),
loggerRef.overrideWith(() => logger),
shorebirdValidatorRef.overrideWith(() => shorebirdValidator),
},
);
}
setUpAll(() {
registerFallbackValue(FakeShorebirdProcess());
registerFallbackValue(Directory(''));
});
setUp(() {
argResults = MockArgResults();
artifactBuilder = MockArtifactBuilder();
doctor = MockDoctor();
logger = MockShorebirdLogger();
flutterValidator = MockShorebirdFlutterValidator();
shorebirdValidator = MockShorebirdValidator();
when(() => argResults.rest).thenReturn([]);
when(() => argResults.wasParsed(any())).thenReturn(false);
when(() => logger.progress(any())).thenReturn(MockProgress());
when(() => logger.info(any())).thenReturn(null);
when(
() => doctor.androidCommandValidators,
).thenReturn([flutterValidator]);
when(
() => shorebirdValidator.validatePreconditions(
checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'),
checkShorebirdInitialized: any(named: 'checkShorebirdInitialized'),
validators: any(named: 'validators'),
),
).thenAnswer((_) async {});
when(
() => artifactBuilder.buildAppBundle(
flavor: any(named: 'flavor'),
target: any(named: 'target'),
args: any(named: 'args'),
),
).thenAnswer((_) async => File(''));
command = runWithOverrides(BuildAppBundleCommand.new)
..testArgResults = argResults;
});
test('has a description', () {
expect(command.description, isNotEmpty);
});
test('exits when validation fails', () async {
final exception = ValidationFailedException();
when(
() => shorebirdValidator.validatePreconditions(
checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'),
checkShorebirdInitialized: any(named: 'checkShorebirdInitialized'),
validators: any(named: 'validators'),
),
).thenThrow(exception);
await expectLater(
runWithOverrides(command.run),
completion(equals(exception.exitCode.code)),
);
verify(
() => shorebirdValidator.validatePreconditions(
checkUserIsAuthenticated: true,
checkShorebirdInitialized: true,
validators: [flutterValidator],
),
).called(1);
});
test('exits with code 70 when building appbundle fails', () async {
when(
() => artifactBuilder.buildAppBundle(
args: any(named: 'args'),
),
).thenThrow(
ArtifactBuildException('Failed to build: oops'),
);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.software.code));
verify(
() => artifactBuilder.buildAppBundle(args: []),
).called(1);
});
group('when platform was specified via arg results rest', () {
setUp(() {
when(() => argResults.rest).thenReturn(['android', '--verbose']);
});
test('exits with code 0 when building appbundle succeeds', () async {
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.success.code));
verify(
() => artifactBuilder.buildAppBundle(
args: ['--verbose'],
),
).called(1);
verify(
() => logger.info(
'''
📦 Generated an app bundle at:
${lightCyan.wrap(p.join('build', 'app', 'outputs', 'bundle', 'release', 'app-release.aab'))}''',
),
).called(1);
});
});
test('exits with code 0 when building appbundle succeeds', () async {
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.success.code));
verify(
() => artifactBuilder.buildAppBundle(
args: [],
),
).called(1);
verify(
() => logger.info(
'''
📦 Generated an app bundle at:
${lightCyan.wrap(p.join('build', 'app', 'outputs', 'bundle', 'release', 'app-release.aab'))}''',
),
).called(1);
});
test(
'exits with code 0 when building appbundle succeeds '
'with flavor and target', () async {
const flavor = 'development';
final target = p.join('lib', 'main_development.dart');
when(() => argResults['flavor']).thenReturn(flavor);
when(() => argResults['target']).thenReturn(target);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.success.code));
verify(
() => artifactBuilder.buildAppBundle(
flavor: flavor,
target: target,
args: [],
),
).called(1);
verify(
() => logger.info(
'''
📦 Generated an app bundle at:
${lightCyan.wrap(p.join('build', 'app', 'outputs', 'bundle', '${flavor}Release', 'app-$flavor-release.aab'))}''',
),
).called(1);
});
test('local-engine and architectures', () async {
expect(
runWithOverrides(() => AndroidArch.availableAndroidArchs.length),
greaterThan(1),
);
expect(
runScoped(
() => AndroidArch.availableAndroidArchs.length,
values: {
engineConfigRef.overrideWith(
() => const EngineConfig(
localEngine: 'android_release_arm64',
localEngineSrcPath: 'path/to/engine/src',
localEngineHost: 'host_release',
),
),
},
),
equals(1),
);
// We only support a few release configs for now.
expect(
() => runScoped(
() => AndroidArch.availableAndroidArchs.length,
values: {
engineConfigRef.overrideWith(
() => const EngineConfig(
localEngine: 'android_debug_unopt',
localEngineSrcPath: 'path/to/engine/src',
localEngineHost: 'host_debug_unopt',
),
),
},
),
throwsException,
);
});
});
}
@@ -1,16 +0,0 @@
import 'package:shorebird_cli/src/commands/build/build.dart';
import 'package:test/test.dart';
void main() {
group('build', () {
late BuildCommand command;
setUp(() {
command = BuildCommand();
});
test('has a description', () async {
expect(command.description, isNotEmpty);
});
});
}
@@ -1,253 +0,0 @@
import 'dart:io';
import 'package:args/args.dart';
import 'package:mason_logger/mason_logger.dart';
import 'package:mocktail/mocktail.dart';
import 'package:path/path.dart' as p;
import 'package:scoped_deps/scoped_deps.dart';
import 'package:shorebird_cli/src/artifact_builder.dart';
import 'package:shorebird_cli/src/commands/build/build.dart';
import 'package:shorebird_cli/src/doctor.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/os/operating_system_interface.dart';
import 'package:shorebird_cli/src/platform/platform.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_validator.dart';
import 'package:shorebird_cli/src/validators/validators.dart';
import 'package:test/test.dart';
import '../../fakes.dart';
import '../../mocks.dart';
void main() {
group(BuildIpaCommand, () {
late ArgResults argResults;
late ArtifactBuilder artifactBuilder;
late Doctor doctor;
late Ios ios;
late ShorebirdLogger logger;
late OperatingSystemInterface operatingSystemInterface;
late BuildIpaCommand command;
late ShorebirdEnv shorebirdEnv;
late ShorebirdFlutterValidator flutterValidator;
late ShorebirdValidator shorebirdValidator;
R runWithOverrides<R>(R Function() body) {
return runScoped(
body,
values: {
artifactBuilderRef.overrideWith(() => artifactBuilder),
doctorRef.overrideWith(() => doctor),
iosRef.overrideWith(() => ios),
loggerRef.overrideWith(() => logger),
osInterfaceRef.overrideWith(() => operatingSystemInterface),
shorebirdEnvRef.overrideWith(() => shorebirdEnv),
shorebirdValidatorRef.overrideWith(() => shorebirdValidator),
},
);
}
setUpAll(() {
registerFallbackValue(FakeShorebirdProcess());
});
setUp(() {
artifactBuilder = MockArtifactBuilder();
argResults = MockArgResults();
doctor = MockDoctor();
ios = MockIos();
logger = MockShorebirdLogger();
operatingSystemInterface = MockOperatingSystemInterface();
flutterValidator = MockShorebirdFlutterValidator();
shorebirdEnv = MockShorebirdEnv();
shorebirdValidator = MockShorebirdValidator();
when(() => argResults['codesign']).thenReturn(true);
when(() => argResults.rest).thenReturn([]);
when(() => argResults.wasParsed(any())).thenReturn(false);
when(
() => artifactBuilder.buildIpa(
flavor: any(named: 'flavor'),
target: any(named: 'target'),
codesign: any(named: 'codesign'),
args: any(named: 'args'),
),
).thenAnswer((_) async => IpaBuildResult(kernelFile: File('')));
when(() => ios.createExportOptionsPlist()).thenReturn(File('.'));
when(() => logger.progress(any())).thenReturn(MockProgress());
when(() => logger.info(any())).thenReturn(null);
when(() => operatingSystemInterface.which('flutter'))
.thenReturn('/path/to/flutter');
when(() => doctor.iosCommandValidators).thenReturn([flutterValidator]);
when(() => shorebirdEnv.flutterRevision).thenReturn('1234');
when(
() => shorebirdValidator.validatePreconditions(
checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'),
checkShorebirdInitialized: any(named: 'checkShorebirdInitialized'),
validators: any(named: 'validators'),
),
).thenAnswer((_) async {});
command = runWithOverrides(BuildIpaCommand.new)
..testArgResults = argResults;
});
test('has a description', () {
expect(command.description, isNotEmpty);
});
test('exits when validation fails', () async {
final exception = ValidationFailedException();
when(
() => shorebirdValidator.validatePreconditions(
checkUserIsAuthenticated: any(named: 'checkUserIsAuthenticated'),
checkShorebirdInitialized: any(named: 'checkShorebirdInitialized'),
validators: any(named: 'validators'),
),
).thenThrow(exception);
await expectLater(
runWithOverrides(command.run),
completion(equals(exception.exitCode.code)),
);
verify(
() => shorebirdValidator.validatePreconditions(
checkUserIsAuthenticated: true,
checkShorebirdInitialized: true,
validators: [flutterValidator],
),
).called(1);
});
test('exits with code 70 when building ipa fails', () async {
when(
() => artifactBuilder.buildIpa(
flavor: any(named: 'flavor'),
target: any(named: 'target'),
codesign: any(named: 'codesign'),
args: any(named: 'args'),
),
).thenThrow(ArtifactBuildException('oops'));
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.software.code));
verify(
() => artifactBuilder.buildIpa(
args: [],
),
).called(1);
});
group('when platform was specified via arg results rest', () {
setUp(() {
when(() => argResults.rest).thenReturn(['ios', '--verbose']);
});
test('exits with code 0 when building ipa succeeds', () async {
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.success.code));
verify(
() => artifactBuilder.buildIpa(args: ['--verbose']),
).called(1);
verifyInOrder([
() => logger.info(
'''
📦 Generated an xcode archive at:
${lightCyan.wrap(p.join('build', 'ios', 'archive', 'Runner.xcarchive'))}''',
),
() => logger.info(
'''
📦 Generated an ipa at:
${lightCyan.wrap(p.join('build', 'ios', 'ipa', 'Runner.ipa'))}''',
),
]);
});
});
test('exits with code 0 when building ipa succeeds', () async {
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.success.code));
verify(() => artifactBuilder.buildIpa(args: [])).called(1);
verifyInOrder([
() => logger.info(
'''
📦 Generated an xcode archive at:
${lightCyan.wrap(p.join('build', 'ios', 'archive', 'Runner.xcarchive'))}''',
),
() => logger.info(
'''
📦 Generated an ipa at:
${lightCyan.wrap(p.join('build', 'ios', 'ipa', 'Runner.ipa'))}''',
),
]);
});
test(
'exits with code 0 when building ipa succeeds '
'with flavor and target', () async {
const flavor = 'development';
final target = p.join('lib', 'main_development.dart');
when(() => argResults['flavor']).thenReturn(flavor);
when(() => argResults['target']).thenReturn(target);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.success.code));
verify(
() => artifactBuilder.buildIpa(
flavor: flavor,
target: target,
args: [],
),
).called(1);
verifyInOrder([
() => logger.info(
'''
📦 Generated an xcode archive at:
${lightCyan.wrap(p.join('build', 'ios', 'archive', 'Runner.xcarchive'))}''',
),
() => logger.info(
'''
📦 Generated an ipa at:
${lightCyan.wrap(p.join('build', 'ios', 'ipa', 'Runner.ipa'))}''',
),
]);
});
test(
'exits with code 0 when building ipa succeeds '
'with --no-codesign', () async {
when(() => argResults['codesign']).thenReturn(false);
final exitCode = await runWithOverrides(command.run);
expect(exitCode, equals(ExitCode.success.code));
verify(
() => artifactBuilder.buildIpa(codesign: false, args: []),
).called(1);
verify(
() => logger.info(
'''
📦 Generated an xcode archive at:
${lightCyan.wrap(p.join('build', 'ios', 'archive', 'Runner.xcarchive'))}''',
),
).called(1);
verifyNever(
() => logger.info(
'''
📦 Generated an ipa at:
${lightCyan.wrap(p.join('build', 'ios', 'ipa', 'Runner.ipa'))}''',
),
);
});
});
}