refactor(shorebird_cli): add iOS artifact lookup methods to ArtifactManager (#2010)

This commit is contained in:
Bryan Oltman
2024-05-07 13:52:19 -04:00
committed by GitHub
parent e9f10d8992
commit e42991c10d
3 changed files with 249 additions and 4 deletions
@@ -2,11 +2,14 @@ import 'dart:io';
import 'dart:isolate';
import 'package:archive/archive_io.dart';
import 'package:collection/collection.dart';
import 'package:http/http.dart' as http;
import 'package:path/path.dart' as p;
import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/cache.dart';
import 'package:shorebird_cli/src/http_client/http_client.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_process.dart';
/// A reference to a [ArtifactManager] instance.
@@ -173,4 +176,88 @@ Failed to create diff (exit code ${result.exitCode}).
return archsDirectory.existsSync() ? archsDirectory : null;
}
/// Returns the .xcarchive directory generated by `flutter build ipa`. This
/// was traditionally named `Runner.xcarchive`, but can now be renamed.
Directory? getXcarchiveDirectory() {
final projectRoot = shorebirdEnv.getShorebirdProjectRoot()!;
final archiveDirectory = Directory(
p.join(
projectRoot.path,
'build',
'ios',
'archive',
),
);
if (!archiveDirectory.existsSync()) return null;
return archiveDirectory
.listSync()
.whereType<Directory>()
.firstWhereOrNull((directory) => directory.path.endsWith('.xcarchive'));
}
/// Returns the .app directory generated by `flutter build ipa`. This was
/// traditionally named `Runner.app`, but can now be renamed.
Directory? getIosAppDirectory({required Directory xcarchiveDirectory}) {
final applicationsDirectory = Directory(
p.join(
xcarchiveDirectory.path,
'Products',
'Applications',
),
);
if (!applicationsDirectory.existsSync()) {
return null;
}
return applicationsDirectory
.listSync()
.whereType<Directory>()
.firstWhereOrNull((directory) => directory.path.endsWith('.app'));
}
/// Returns the path to the .ipa file generated by `flutter build ipa`.
///
/// Returns null if:
/// - there is no ipa build directory (build/ios/ipa)
/// - there is no .ipa file in the ipa build directory
/// - there is more than one .ipa file in the ipa build directory
File? getIpa() {
final projectRoot = shorebirdEnv.getShorebirdProjectRoot()!;
final ipaBuildDirectory = Directory(
p.join(
projectRoot.path,
'build',
'ios',
'ipa',
),
);
if (!ipaBuildDirectory.existsSync()) {
logger.detail('No directory found at ${ipaBuildDirectory.path}');
return null;
}
final ipaFiles = ipaBuildDirectory
.listSync(recursive: true)
.whereType<File>()
.where((f) => p.extension(f.path) == '.ipa');
if (ipaFiles.isEmpty) {
logger.detail('No .ipa files found in ${ipaBuildDirectory.path}');
return null;
}
if (ipaFiles.length > 1) {
logger.detail(
'More than one .ipa file found in ${ipaBuildDirectory.path}',
);
return null;
}
return ipaFiles.single;
}
}
@@ -512,9 +512,7 @@ Either run `flutter pub get` manually, or follow the steps in ${link(uri: Uri.pa
final tempDir = Directory.systemTemp.createTempSync();
exportOptionsPlist =
File(p.join(tempDir.path, 'exportoptions.plist'));
when(ios.createExportOptionsPlist).thenReturn(
exportOptionsPlist,
);
when(ios.createExportOptionsPlist).thenReturn(exportOptionsPlist);
});
group('with default arguments', () {
@@ -8,6 +8,8 @@ import 'package:scoped/scoped.dart';
import 'package:shorebird_cli/src/artifact_manager.dart';
import 'package:shorebird_cli/src/cache.dart';
import 'package:shorebird_cli/src/http_client/http_client.dart';
import 'package:shorebird_cli/src/logger.dart';
import 'package:shorebird_cli/src/shorebird_env.dart';
import 'package:shorebird_cli/src/shorebird_process.dart';
import 'package:test/test.dart';
@@ -19,6 +21,9 @@ void main() {
late Cache cache;
late Directory cacheArtifactDirectory;
late http.Client httpClient;
late Directory projectRoot;
late Logger logger;
late ShorebirdEnv shorebirdEnv;
late ShorebirdProcessResult patchProcessResult;
late ShorebirdProcess shorebirdProcess;
late ArtifactManager artifactManager;
@@ -29,7 +34,9 @@ void main() {
values: {
cacheRef.overrideWith(() => cache),
httpClientRef.overrideWith(() => httpClient),
loggerRef.overrideWith(() => logger),
processRef.overrideWith(() => shorebirdProcess),
shorebirdEnvRef.overrideWith(() => shorebirdEnv),
},
);
}
@@ -42,9 +49,11 @@ void main() {
cacheArtifactDirectory = Directory.systemTemp.createTempSync();
cache = MockCache();
httpClient = MockHttpClient();
logger = MockLogger();
patchProcessResult = MockProcessResult();
projectRoot = Directory.systemTemp.createTempSync();
shorebirdProcess = MockShorebirdProcess();
artifactManager = ArtifactManager();
shorebirdEnv = MockShorebirdEnv();
when(() => cache.getArtifactDirectory(any()))
.thenReturn(cacheArtifactDirectory);
@@ -53,6 +62,10 @@ void main() {
when(() => httpClient.send(any())).thenAnswer(
(_) async => http.StreamedResponse(const Stream.empty(), HttpStatus.ok),
);
when(() => shorebirdEnv.getShorebirdProjectRoot())
.thenReturn(projectRoot);
when(
() => shorebirdProcess.run(
any(that: endsWith('patch')),
@@ -68,6 +81,8 @@ void main() {
return patchProcessResult;
});
when(() => patchProcessResult.exitCode).thenReturn(ExitCode.success.code);
artifactManager = ArtifactManager();
});
group('createDiff', () {
@@ -399,5 +414,150 @@ void main() {
});
});
});
group('getXcarchiveDirectory', () {
group('when archive directory exists', () {
late Directory archiveDirectory;
setUp(() {
archiveDirectory = Directory(
p.join(
projectRoot.path,
'build',
'ios',
'archive',
'Runner.xcarchive',
),
)..createSync(recursive: true);
});
test('returns path to archive directory', () async {
final result = runWithOverrides(
() => artifactManager.getXcarchiveDirectory(),
);
expect(result, isNotNull);
expect(result!.path, equals(archiveDirectory.path));
});
});
group('when archive directory does not exist', () {
test('returns null', () {
expect(
runWithOverrides(artifactManager.getXcarchiveDirectory),
isNull,
);
});
});
});
group('getIosAppDirectory', () {
group('when applications directory does not exist', () {
test('returns null', () {
final xcarchiveDirectory = Directory.systemTemp.createTempSync();
expect(
runWithOverrides(
() => artifactManager.getIosAppDirectory(
xcarchiveDirectory: xcarchiveDirectory,
),
),
isNull,
);
});
});
group('when applications directory exists', () {
late Directory applicationsDirectory;
late Directory xcarchiveDirectory;
setUp(() {
xcarchiveDirectory = Directory.systemTemp.createTempSync();
applicationsDirectory = Directory(
p.join(
xcarchiveDirectory.path,
'Products',
'Applications',
'Runner.app',
),
)..createSync(recursive: true);
});
test('returns path to applications directory', () {
final result = runWithOverrides(
() => artifactManager.getIosAppDirectory(
xcarchiveDirectory: xcarchiveDirectory,
),
);
expect(result, isNotNull);
expect(result!.path, equals(applicationsDirectory.path));
});
});
});
group('getIpa', () {
group('when ipa build directory does not exist', () {
test('returns null', () {
expect(
runWithOverrides(artifactManager.getIpa),
isNull,
);
});
});
group('when ipa build directory exists', () {
late Directory ipaBuildDirectory;
late File ipaFile;
setUp(() {
ipaBuildDirectory = Directory(
p.join(
projectRoot.path,
'build',
'ios',
'ipa',
),
)..createSync(recursive: true);
ipaFile = File(p.join(ipaBuildDirectory.path, 'Runner.ipa'))
..createSync();
});
test('returns path to ipa file', () {
final result = runWithOverrides(artifactManager.getIpa);
expect(result, isNotNull);
expect(result!.path, equals(ipaFile.path));
});
test('returns null when multiple ipa files exist', () {
File(p.join(ipaBuildDirectory.path, 'Runner2.ipa')).createSync();
expect(
runWithOverrides(artifactManager.getIpa),
isNull,
);
verify(
() => logger.detail(
'More than one .ipa file found in ${ipaBuildDirectory.path}',
),
);
});
test('returns null when no ipa files exist', () {
ipaFile.deleteSync();
expect(
runWithOverrides(artifactManager.getIpa),
isNull,
);
verify(
() => logger.detail(
'No .ipa files found in ${ipaBuildDirectory.path}',
),
);
});
});
});
});
}