feat(shorebird_cli): support asset diffing in iOS frameworks (#1046)
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
export 'android_archive_differ.dart';
|
||||
export 'file_set_diff.dart';
|
||||
export 'ios_archive_differ.dart';
|
||||
export 'ipa.dart';
|
||||
export 'ipa_differ.dart';
|
||||
|
||||
+2
-2
@@ -2,7 +2,7 @@ import 'package:path/path.dart' as p;
|
||||
import 'package:shorebird_cli/src/archive_analysis/archive_differ.dart';
|
||||
import 'package:shorebird_cli/src/archive_analysis/file_set_diff.dart';
|
||||
|
||||
/// Finds differences between two IPAs.
|
||||
/// Finds differences between two IPAs or zipped Xcframeworks.
|
||||
///
|
||||
/// Asset changes will be in the `Assets.car` file (which is a combination of
|
||||
/// the `.xcasset` catalogs in the Xcode project) and the `flutter_assets`
|
||||
@@ -12,7 +12,7 @@ import 'package:shorebird_cli/src/archive_analysis/file_set_diff.dart';
|
||||
/// Flutter.framework or App.framework files.
|
||||
///
|
||||
/// Dart changes will appear in the App.framework/App executable.
|
||||
class IpaDiffer extends ArchiveDiffer {
|
||||
class IosArchiveDiffer extends ArchiveDiffer {
|
||||
static const binaryFiles = {
|
||||
'App.framework/App',
|
||||
'Flutter.framework/Flutter',
|
||||
@@ -30,10 +30,10 @@ class PatchIosCommand extends ShorebirdCommand
|
||||
/// {@macro patch_ios_command}
|
||||
PatchIosCommand({
|
||||
HashFunction? hashFn,
|
||||
IpaDiffer? ipaDiffer,
|
||||
IosArchiveDiffer? archiveDiffer,
|
||||
IpaReader? ipaReader,
|
||||
}) : _hashFn = hashFn ?? ((m) => sha256.convert(m).toString()),
|
||||
_ipaDiffer = ipaDiffer ?? IpaDiffer(),
|
||||
_archiveDiffer = archiveDiffer ?? IosArchiveDiffer(),
|
||||
_ipaReader = ipaReader ?? IpaReader() {
|
||||
argParser
|
||||
..addOption(
|
||||
@@ -71,7 +71,7 @@ class PatchIosCommand extends ShorebirdCommand
|
||||
'Publish new patches for a specific iOS release to Shorebird.';
|
||||
|
||||
final HashFunction _hashFn;
|
||||
final IpaDiffer _ipaDiffer;
|
||||
final IosArchiveDiffer _archiveDiffer;
|
||||
final IpaReader _ipaReader;
|
||||
|
||||
@override
|
||||
@@ -235,7 +235,7 @@ Please re-run the release command for this version or create a new release.''');
|
||||
await patchDiffChecker.confirmUnpatchableDiffsIfNecessary(
|
||||
localArtifact: File(ipaPath),
|
||||
releaseArtifactUrl: Uri.parse(releaseArtifact.url),
|
||||
archiveDiffer: _ipaDiffer,
|
||||
archiveDiffer: _archiveDiffer,
|
||||
force: force,
|
||||
);
|
||||
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import 'dart:io' hide Platform;
|
||||
|
||||
import 'package:archive/archive_io.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:platform/platform.dart';
|
||||
import 'package:scoped/scoped.dart';
|
||||
import 'package:shorebird_cli/src/archive_analysis/archive_analysis.dart';
|
||||
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
|
||||
import 'package:shorebird_cli/src/command.dart';
|
||||
import 'package:shorebird_cli/src/config/shorebird_yaml.dart';
|
||||
@@ -12,6 +15,7 @@ import 'package:shorebird_cli/src/doctor.dart';
|
||||
import 'package:shorebird_cli/src/formatters/file_size_formatter.dart';
|
||||
import 'package:shorebird_cli/src/ios.dart';
|
||||
import 'package:shorebird_cli/src/logger.dart';
|
||||
import 'package:shorebird_cli/src/patch_diff_checker.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_artifact_mixin.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_build_mixin.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_env.dart';
|
||||
@@ -23,7 +27,9 @@ class PatchIosFrameworkCommand extends ShorebirdCommand
|
||||
with ShorebirdBuildMixin, ShorebirdArtifactMixin {
|
||||
PatchIosFrameworkCommand({
|
||||
HashFunction? hashFn,
|
||||
}) : _hashFn = hashFn ?? ((m) => sha256.convert(m).toString()) {
|
||||
IosArchiveDiffer? archiveDiffer,
|
||||
}) : _hashFn = hashFn ?? ((m) => sha256.convert(m).toString()),
|
||||
_archiveDiffer = archiveDiffer ?? IosArchiveDiffer() {
|
||||
argParser
|
||||
..addOption(
|
||||
'release-version',
|
||||
@@ -46,6 +52,7 @@ of the iOS app that is using this module.''',
|
||||
}
|
||||
|
||||
final HashFunction _hashFn;
|
||||
final IosArchiveDiffer _archiveDiffer;
|
||||
|
||||
@override
|
||||
String get name => 'ios-framework-alpha';
|
||||
@@ -158,6 +165,36 @@ Please re-run the release command for this version or create a new release.''');
|
||||
|
||||
buildProgress.complete();
|
||||
|
||||
const zippedFrameworkFileName =
|
||||
'${ShorebirdArtifactMixin.appXcframeworkName}.zip';
|
||||
final tempDir = Directory.systemTemp.createTempSync();
|
||||
final zippedFrameworkPath = p.join(
|
||||
tempDir.path,
|
||||
zippedFrameworkFileName,
|
||||
);
|
||||
ZipFileEncoder().zipDirectory(
|
||||
Directory(getAppXcframeworkPath()),
|
||||
filename: zippedFrameworkPath,
|
||||
);
|
||||
|
||||
final releaseArtifact = await codePushClientWrapper.getReleaseArtifact(
|
||||
appId: appId,
|
||||
releaseId: release.id,
|
||||
arch: 'xcframework',
|
||||
platform: ReleasePlatform.ios,
|
||||
);
|
||||
final shouldContinue =
|
||||
await patchDiffChecker.confirmUnpatchableDiffsIfNecessary(
|
||||
localArtifact: File(zippedFrameworkPath),
|
||||
releaseArtifactUrl: Uri.parse(releaseArtifact.url),
|
||||
archiveDiffer: _archiveDiffer,
|
||||
force: force,
|
||||
);
|
||||
|
||||
if (!shouldContinue) {
|
||||
return ExitCode.success.code;
|
||||
}
|
||||
|
||||
if (dryRun) {
|
||||
logger
|
||||
..info('No issues detected.')
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import 'dart:io' hide Platform;
|
||||
|
||||
import 'package:mason_logger/mason_logger.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:platform/platform.dart';
|
||||
@@ -9,13 +7,14 @@ import 'package:shorebird_cli/src/config/config.dart';
|
||||
import 'package:shorebird_cli/src/doctor.dart';
|
||||
import 'package:shorebird_cli/src/ios.dart';
|
||||
import 'package:shorebird_cli/src/logger.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_artifact_mixin.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_build_mixin.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_env.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_validator.dart';
|
||||
import 'package:shorebird_code_push_client/shorebird_code_push_client.dart';
|
||||
|
||||
class ReleaseIosFrameworkCommand extends ShorebirdCommand
|
||||
with ShorebirdBuildMixin {
|
||||
with ShorebirdArtifactMixin, ShorebirdBuildMixin {
|
||||
ReleaseIosFrameworkCommand() {
|
||||
argParser
|
||||
..addOption(
|
||||
@@ -119,16 +118,10 @@ ${summary.join('\n')}
|
||||
);
|
||||
}
|
||||
|
||||
final iosBuildDir = p.join(Directory.current.path, 'build', 'ios');
|
||||
final frameworkDirectory = Directory(
|
||||
p.join(iosBuildDir, 'framework', 'Release'),
|
||||
);
|
||||
final xcframeworkPath = p.join(frameworkDirectory.path, 'App.xcframework');
|
||||
|
||||
await codePushClientWrapper.createIosFrameworkReleaseArtifacts(
|
||||
appId: appId,
|
||||
releaseId: release.id,
|
||||
appFrameworkPath: xcframeworkPath,
|
||||
appFrameworkPath: getAppXcframeworkPath(),
|
||||
);
|
||||
|
||||
await codePushClientWrapper.updateReleaseStatus(
|
||||
@@ -138,7 +131,8 @@ ${summary.join('\n')}
|
||||
status: ReleaseStatus.active,
|
||||
);
|
||||
|
||||
final relativeFrameworkDirectoryPath = p.relative(frameworkDirectory.path);
|
||||
final relativeFrameworkDirectoryPath =
|
||||
p.relative(getAppXcframeworkDirectory().path);
|
||||
logger
|
||||
..success('\n✅ Published Release!')
|
||||
..info('''
|
||||
|
||||
@@ -96,6 +96,28 @@ mixin ShorebirdArtifactMixin on ShorebirdCommand {
|
||||
return ipaFiles.single.path;
|
||||
}
|
||||
|
||||
static const String appXcframeworkName = 'App.xcframework';
|
||||
|
||||
/// Returns the path to the App.xcframework generated by
|
||||
/// `shorebird release ios-framework-alpha` or
|
||||
/// `shorebird patch ios-framework-alpha`.
|
||||
String getAppXcframeworkPath() {
|
||||
return p.join(getAppXcframeworkDirectory().path, appXcframeworkName);
|
||||
}
|
||||
|
||||
/// Returns the [Directory] containing the App.xcframework generated by
|
||||
/// `shorebird release ios-framework-alpha` or
|
||||
/// `shorebird patch ios-framework-alpha`.
|
||||
Directory getAppXcframeworkDirectory() => Directory(
|
||||
p.join(
|
||||
Directory.current.path,
|
||||
'build',
|
||||
'ios',
|
||||
'framework',
|
||||
'Release',
|
||||
),
|
||||
);
|
||||
|
||||
/// Finds the most recently-edited app.dill file in the .dart_tool directory.
|
||||
// TODO(bryanoltman): This is an enormous hack – we don't know that this is
|
||||
// the correct file.
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
The xcframework files in this folder were generated by building the stock Flutter counter app with `shorebird release ios-framework-alpha` and zipped with the `ditto` command.
|
||||
|
||||
Files:
|
||||
|
||||
- base.xcframework.zip is meant to represent an xcframework uploaded as part of a release.
|
||||
- changed_asset.xcframework.zip is meant to represent an xcframework generated with a changed asset file.
|
||||
- changed_dart.xcframework.zip is meant to represent an xcframework generated with a changed dart file.
|
||||
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,168 @@
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:shorebird_cli/src/archive_analysis/archive_analysis.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
final ipaFixturesBasePath = p.join('test', 'fixtures', 'ipas');
|
||||
final baseIpaPath = p.join(ipaFixturesBasePath, 'base.ipa');
|
||||
final changedAssetIpaPath = p.join(ipaFixturesBasePath, 'asset_changes.ipa');
|
||||
final changedDartIpaPath = p.join(ipaFixturesBasePath, 'dart_changes.ipa');
|
||||
final changedSwiftIpaPath = p.join(ipaFixturesBasePath, 'swift_changes.ipa');
|
||||
|
||||
final xcframeworkFixturesBasePath = p.join(
|
||||
'test',
|
||||
'fixtures',
|
||||
'xcframeworks',
|
||||
);
|
||||
final baseXcframeworkPath =
|
||||
p.join(xcframeworkFixturesBasePath, 'base.xcframework.zip');
|
||||
final changedAssetXcframeworkPath =
|
||||
p.join(xcframeworkFixturesBasePath, 'changed_asset.xcframework.zip');
|
||||
final changedDartXcframeworkPath =
|
||||
p.join(xcframeworkFixturesBasePath, 'changed_dart.xcframework.zip');
|
||||
|
||||
group(IosArchiveDiffer, () {
|
||||
late IosArchiveDiffer differ;
|
||||
|
||||
setUp(() {
|
||||
differ = IosArchiveDiffer();
|
||||
});
|
||||
|
||||
group('ipa', () {
|
||||
group('changedPaths', () {
|
||||
test('finds no differences between the same ipa', () {
|
||||
expect(differ.changedFiles(baseIpaPath, baseIpaPath), isEmpty);
|
||||
});
|
||||
|
||||
test('finds differences between two different ipas', () {
|
||||
expect(
|
||||
differ.changedFiles(baseIpaPath, changedAssetIpaPath).changedPaths,
|
||||
{
|
||||
'Payload/Runner.app/_CodeSignature/CodeResources',
|
||||
'Payload/Runner.app/Runner',
|
||||
'Payload/Runner.app/Frameworks/Flutter.framework/Flutter',
|
||||
'Payload/Runner.app/Frameworks/App.framework/_CodeSignature/CodeResources',
|
||||
'Payload/Runner.app/Frameworks/App.framework/App',
|
||||
'Payload/Runner.app/Frameworks/App.framework/flutter_assets/assets/asset.json',
|
||||
'Symbols/4C4C4411-5555-3144-A13A-E47369D8ACD5.symbols',
|
||||
'Symbols/BC970605-0A53-3457-8736-D7A870AB6E71.symbols',
|
||||
'Symbols/0CBBC9EF-0745-3074-81B7-765F5B4515FD.symbols',
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('changedFiles', () {
|
||||
test('detects asset changes', () {
|
||||
final fileSetDiff =
|
||||
differ.changedFiles(baseIpaPath, changedAssetIpaPath);
|
||||
expect(differ.assetsFileSetDiff(fileSetDiff), isNotEmpty);
|
||||
expect(differ.dartFileSetDiff(fileSetDiff), isNotEmpty);
|
||||
expect(differ.nativeFileSetDiff(fileSetDiff), isNotEmpty);
|
||||
});
|
||||
|
||||
test('detects dart changes', () {
|
||||
final fileSetDiff =
|
||||
differ.changedFiles(baseIpaPath, changedDartIpaPath);
|
||||
expect(differ.assetsFileSetDiff(fileSetDiff), isEmpty);
|
||||
expect(differ.dartFileSetDiff(fileSetDiff), isNotEmpty);
|
||||
expect(differ.nativeFileSetDiff(fileSetDiff), isNotEmpty);
|
||||
});
|
||||
|
||||
test('detects swift changes', () {
|
||||
final fileSetDiff =
|
||||
differ.changedFiles(baseIpaPath, changedSwiftIpaPath);
|
||||
expect(differ.assetsFileSetDiff(fileSetDiff), isEmpty);
|
||||
expect(differ.dartFileSetDiff(fileSetDiff), isNotEmpty);
|
||||
expect(differ.nativeFileSetDiff(fileSetDiff), isNotEmpty);
|
||||
});
|
||||
});
|
||||
|
||||
group('containsPotentiallyBreakingAssetDiffs', () {
|
||||
test('returns true if a file in flutter_assets has changed', () {
|
||||
final fileSetDiff = differ.changedFiles(
|
||||
baseIpaPath,
|
||||
changedAssetIpaPath,
|
||||
);
|
||||
expect(
|
||||
differ.containsPotentiallyBreakingAssetDiffs(fileSetDiff),
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
|
||||
test('returns false if no files in flutter_assets has changed', () {
|
||||
final fileSetDiff = differ.changedFiles(
|
||||
baseIpaPath,
|
||||
changedDartIpaPath,
|
||||
);
|
||||
expect(
|
||||
differ.containsPotentiallyBreakingAssetDiffs(fileSetDiff),
|
||||
isFalse,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('containsPotentiallyBreakingNativeDiffs', () {
|
||||
test("always returns false, as we don't check for this yet", () {
|
||||
final fileSetDiff = differ.changedFiles(
|
||||
baseIpaPath,
|
||||
changedSwiftIpaPath,
|
||||
);
|
||||
expect(
|
||||
differ.containsPotentiallyBreakingNativeDiffs(fileSetDiff),
|
||||
isFalse,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('xcframework', () {
|
||||
group('changedPaths', () {
|
||||
test('finds no differences between the same zipped xcframeworks', () {
|
||||
expect(
|
||||
differ.changedFiles(baseXcframeworkPath, baseXcframeworkPath),
|
||||
isEmpty,
|
||||
);
|
||||
});
|
||||
|
||||
test('finds differences between two differed zipped xcframeworks', () {
|
||||
expect(
|
||||
differ
|
||||
.changedFiles(baseXcframeworkPath, changedAssetXcframeworkPath)
|
||||
.changedPaths,
|
||||
{
|
||||
'ios-arm64_x86_64-simulator/App.framework/_CodeSignature/CodeResources',
|
||||
'ios-arm64_x86_64-simulator/App.framework/App',
|
||||
'ios-arm64_x86_64-simulator/App.framework/flutter_assets/assets/asset.json',
|
||||
'ios-arm64/App.framework/_CodeSignature/CodeResources',
|
||||
'ios-arm64/App.framework/App',
|
||||
'ios-arm64/App.framework/flutter_assets/assets/asset.json'
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('changedFiles', () {
|
||||
test('detects asset changes', () {
|
||||
final fileSetDiff = differ.changedFiles(
|
||||
baseXcframeworkPath,
|
||||
changedAssetXcframeworkPath,
|
||||
);
|
||||
expect(differ.assetsFileSetDiff(fileSetDiff), isNotEmpty);
|
||||
expect(differ.dartFileSetDiff(fileSetDiff), isNotEmpty);
|
||||
expect(differ.nativeFileSetDiff(fileSetDiff), isEmpty);
|
||||
});
|
||||
|
||||
test('detects dart changes', () {
|
||||
final fileSetDiff = differ.changedFiles(
|
||||
baseXcframeworkPath,
|
||||
changedDartXcframeworkPath,
|
||||
);
|
||||
expect(differ.assetsFileSetDiff(fileSetDiff), isEmpty);
|
||||
expect(differ.dartFileSetDiff(fileSetDiff), isNotEmpty);
|
||||
expect(differ.nativeFileSetDiff(fileSetDiff), isEmpty);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:shorebird_cli/src/archive_analysis/archive_analysis.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
final ipaFixturesBasePath = p.join('test', 'fixtures', 'ipas');
|
||||
final baseIpaPath = p.join(ipaFixturesBasePath, 'base.ipa');
|
||||
final changedAssetIpaPath = p.join(ipaFixturesBasePath, 'asset_changes.ipa');
|
||||
final changedDartIpaPath = p.join(ipaFixturesBasePath, 'dart_changes.ipa');
|
||||
final changedSwiftIpaPath = p.join(ipaFixturesBasePath, 'swift_changes.ipa');
|
||||
|
||||
late IpaDiffer differ;
|
||||
|
||||
setUp(() {
|
||||
differ = IpaDiffer();
|
||||
});
|
||||
|
||||
group(IpaDiffer, () {
|
||||
group('changedPaths', () {
|
||||
test('finds no differences between the same ipa', () {
|
||||
expect(differ.changedFiles(baseIpaPath, baseIpaPath), isEmpty);
|
||||
});
|
||||
|
||||
test('finds differences between two different ipas', () {
|
||||
expect(
|
||||
differ.changedFiles(baseIpaPath, changedAssetIpaPath).changedPaths,
|
||||
{
|
||||
'Payload/Runner.app/_CodeSignature/CodeResources',
|
||||
'Payload/Runner.app/Runner',
|
||||
'Payload/Runner.app/Frameworks/Flutter.framework/Flutter',
|
||||
'Payload/Runner.app/Frameworks/App.framework/_CodeSignature/CodeResources',
|
||||
'Payload/Runner.app/Frameworks/App.framework/App',
|
||||
'Payload/Runner.app/Frameworks/App.framework/flutter_assets/assets/asset.json',
|
||||
'Symbols/4C4C4411-5555-3144-A13A-E47369D8ACD5.symbols',
|
||||
'Symbols/BC970605-0A53-3457-8736-D7A870AB6E71.symbols',
|
||||
'Symbols/0CBBC9EF-0745-3074-81B7-765F5B4515FD.symbols',
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('changedFiles', () {
|
||||
test('detects asset changes', () {
|
||||
final fileSetDiff =
|
||||
differ.changedFiles(baseIpaPath, changedAssetIpaPath);
|
||||
expect(differ.assetsFileSetDiff(fileSetDiff), isNotEmpty);
|
||||
expect(differ.dartFileSetDiff(fileSetDiff), isNotEmpty);
|
||||
expect(differ.nativeFileSetDiff(fileSetDiff), isNotEmpty);
|
||||
});
|
||||
|
||||
test('detects dart changes', () {
|
||||
final fileSetDiff =
|
||||
differ.changedFiles(baseIpaPath, changedDartIpaPath);
|
||||
expect(differ.assetsFileSetDiff(fileSetDiff), isEmpty);
|
||||
expect(differ.dartFileSetDiff(fileSetDiff), isNotEmpty);
|
||||
expect(differ.nativeFileSetDiff(fileSetDiff), isNotEmpty);
|
||||
});
|
||||
|
||||
test('detects swift changes', () {
|
||||
final fileSetDiff =
|
||||
differ.changedFiles(baseIpaPath, changedSwiftIpaPath);
|
||||
expect(differ.assetsFileSetDiff(fileSetDiff), isEmpty);
|
||||
expect(differ.dartFileSetDiff(fileSetDiff), isNotEmpty);
|
||||
expect(differ.nativeFileSetDiff(fileSetDiff), isNotEmpty);
|
||||
});
|
||||
});
|
||||
|
||||
group('containsPotentiallyBreakingAssetDiffs', () {
|
||||
test('returns true if a file in flutter_assets has changed', () {
|
||||
final fileSetDiff = differ.changedFiles(
|
||||
baseIpaPath,
|
||||
changedAssetIpaPath,
|
||||
);
|
||||
expect(
|
||||
differ.containsPotentiallyBreakingAssetDiffs(fileSetDiff),
|
||||
isTrue,
|
||||
);
|
||||
});
|
||||
|
||||
test('returns false if no files in flutter_assets has changed', () {
|
||||
final fileSetDiff = differ.changedFiles(
|
||||
baseIpaPath,
|
||||
changedDartIpaPath,
|
||||
);
|
||||
expect(
|
||||
differ.containsPotentiallyBreakingAssetDiffs(fileSetDiff),
|
||||
isFalse,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('containsPotentiallyBreakingNativeDiffs', () {
|
||||
test("always returns false, as we don't check for this yet", () {
|
||||
final fileSetDiff = differ.changedFiles(
|
||||
baseIpaPath,
|
||||
changedSwiftIpaPath,
|
||||
);
|
||||
expect(
|
||||
differ.containsPotentiallyBreakingNativeDiffs(fileSetDiff),
|
||||
isFalse,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -39,7 +39,7 @@ class _MockDoctor extends Mock implements Doctor {}
|
||||
|
||||
class _MockIpa extends Mock implements Ipa {}
|
||||
|
||||
class _MockIpaDiffer extends Mock implements IpaDiffer {}
|
||||
class _MockIpaDiffer extends Mock implements IosArchiveDiffer {}
|
||||
|
||||
class _MockIpaReader extends Mock implements IpaReader {}
|
||||
|
||||
@@ -130,7 +130,7 @@ flutter:
|
||||
late File genSnapshotFile;
|
||||
late Doctor doctor;
|
||||
late Ipa ipa;
|
||||
late IpaDiffer ipaDiffer;
|
||||
late IosArchiveDiffer archiveDiffer;
|
||||
late IpaReader ipaReader;
|
||||
late Progress progress;
|
||||
late Logger logger;
|
||||
@@ -232,7 +232,7 @@ flutter:
|
||||
),
|
||||
);
|
||||
ipa = _MockIpa();
|
||||
ipaDiffer = _MockIpaDiffer();
|
||||
archiveDiffer = _MockIpaDiffer();
|
||||
ipaReader = _MockIpaReader();
|
||||
progress = _MockProgress();
|
||||
logger = _MockLogger();
|
||||
@@ -328,13 +328,14 @@ flutter:
|
||||
() => patchDiffChecker.confirmUnpatchableDiffsIfNecessary(
|
||||
localArtifact: any(named: 'localArtifact'),
|
||||
releaseArtifactUrl: any(named: 'releaseArtifactUrl'),
|
||||
archiveDiffer: ipaDiffer,
|
||||
archiveDiffer: archiveDiffer,
|
||||
force: any(named: 'force'),
|
||||
),
|
||||
).thenAnswer((_) async => true);
|
||||
|
||||
command = runWithOverrides(
|
||||
() => PatchIosCommand(ipaDiffer: ipaDiffer, ipaReader: ipaReader),
|
||||
() =>
|
||||
PatchIosCommand(archiveDiffer: archiveDiffer, ipaReader: ipaReader),
|
||||
)..testArgResults = argResults;
|
||||
});
|
||||
|
||||
@@ -774,7 +775,7 @@ Please re-run the release command for this version or create a new release.'''),
|
||||
() => patchDiffChecker.confirmUnpatchableDiffsIfNecessary(
|
||||
localArtifact: any(named: 'localArtifact'),
|
||||
releaseArtifactUrl: any(named: 'releaseArtifactUrl'),
|
||||
archiveDiffer: ipaDiffer,
|
||||
archiveDiffer: archiveDiffer,
|
||||
force: any(named: 'force'),
|
||||
),
|
||||
).thenAnswer((_) async => false);
|
||||
@@ -791,7 +792,7 @@ Please re-run the release command for this version or create a new release.'''),
|
||||
() => patchDiffChecker.confirmUnpatchableDiffsIfNecessary(
|
||||
localArtifact: any(named: 'localArtifact'),
|
||||
releaseArtifactUrl: Uri.parse(ipaArtifact.url),
|
||||
archiveDiffer: ipaDiffer,
|
||||
archiveDiffer: archiveDiffer,
|
||||
force: false,
|
||||
),
|
||||
).called(1);
|
||||
|
||||
+92
-2
@@ -6,12 +6,14 @@ import 'package:mocktail/mocktail.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:platform/platform.dart';
|
||||
import 'package:scoped/scoped.dart';
|
||||
import 'package:shorebird_cli/src/archive_analysis/archive_analysis.dart';
|
||||
import 'package:shorebird_cli/src/auth/auth.dart';
|
||||
import 'package:shorebird_cli/src/code_push_client_wrapper.dart';
|
||||
import 'package:shorebird_cli/src/commands/patch/patch.dart';
|
||||
import 'package:shorebird_cli/src/config/config.dart';
|
||||
import 'package:shorebird_cli/src/doctor.dart';
|
||||
import 'package:shorebird_cli/src/logger.dart';
|
||||
import 'package:shorebird_cli/src/patch_diff_checker.dart';
|
||||
import 'package:shorebird_cli/src/platform.dart';
|
||||
import 'package:shorebird_cli/src/process.dart';
|
||||
import 'package:shorebird_cli/src/shorebird_env.dart';
|
||||
@@ -30,8 +32,12 @@ class _MockCodePushClientWrapper extends Mock
|
||||
|
||||
class _MockDoctor extends Mock implements Doctor {}
|
||||
|
||||
class _MockIosArchiveDiffer extends Mock implements IosArchiveDiffer {}
|
||||
|
||||
class _MockLogger extends Mock implements Logger {}
|
||||
|
||||
class _MockPatchDiffChecker extends Mock implements PatchDiffChecker {}
|
||||
|
||||
class _MockPlatform extends Mock implements Platform {}
|
||||
|
||||
class _MockProgress extends Mock implements Progress {}
|
||||
@@ -72,6 +78,16 @@ flutter:
|
||||
assets:
|
||||
- shorebird.yaml''';
|
||||
const appMetadata = AppMetadata(appId: appId, displayName: appDisplayName);
|
||||
const arch = 'aarch64';
|
||||
const xcframeworkArtifact = ReleaseArtifact(
|
||||
id: 0,
|
||||
releaseId: 0,
|
||||
arch: arch,
|
||||
platform: ReleasePlatform.ios,
|
||||
hash: '#',
|
||||
size: 42,
|
||||
url: 'https://example.com/release.xcframework',
|
||||
);
|
||||
const release = Release(
|
||||
id: 0,
|
||||
appId: appId,
|
||||
@@ -87,6 +103,8 @@ flutter:
|
||||
late Directory flutterDirectory;
|
||||
late File genSnapshotFile;
|
||||
late Doctor doctor;
|
||||
late IosArchiveDiffer archiveDiffer;
|
||||
late PatchDiffChecker patchDiffChecker;
|
||||
late Platform platform;
|
||||
late Auth auth;
|
||||
late Progress progress;
|
||||
@@ -108,6 +126,7 @@ flutter:
|
||||
codePushClientWrapperRef.overrideWith(() => codePushClientWrapper),
|
||||
doctorRef.overrideWith(() => doctor),
|
||||
loggerRef.overrideWith(() => logger),
|
||||
patchDiffCheckerRef.overrideWith(() => patchDiffChecker),
|
||||
platformRef.overrideWith(() => platform),
|
||||
processRef.overrideWith(() => shorebirdProcess),
|
||||
shorebirdEnvRef.overrideWith(() => shorebirdEnv),
|
||||
@@ -136,6 +155,18 @@ flutter:
|
||||
File(p.join(dir.path, 'build', elfAotSnapshotFileName)).createSync(
|
||||
recursive: true,
|
||||
);
|
||||
Directory(
|
||||
p.join(
|
||||
dir.path,
|
||||
'build',
|
||||
'ios',
|
||||
'framework',
|
||||
'Release',
|
||||
'App.xcframework',
|
||||
),
|
||||
).createSync(
|
||||
recursive: true,
|
||||
);
|
||||
}
|
||||
|
||||
Directory setUpTempDir() {
|
||||
@@ -150,13 +181,17 @@ flutter:
|
||||
}
|
||||
|
||||
setUpAll(() {
|
||||
registerFallbackValue(File(''));
|
||||
registerFallbackValue(ReleasePlatform.ios);
|
||||
registerFallbackValue(Uri.parse('https://example.com'));
|
||||
});
|
||||
|
||||
setUp(() {
|
||||
argResults = _MockArgResults();
|
||||
archiveDiffer = _MockIosArchiveDiffer();
|
||||
codePushClientWrapper = _MockCodePushClientWrapper();
|
||||
doctor = _MockDoctor();
|
||||
patchDiffChecker = _MockPatchDiffChecker();
|
||||
platform = _MockPlatform();
|
||||
shorebirdRoot = Directory.systemTemp.createTempSync();
|
||||
flutterDirectory = Directory(
|
||||
@@ -224,6 +259,14 @@ flutter:
|
||||
when(
|
||||
() => codePushClientWrapper.getReleases(appId: any(named: 'appId')),
|
||||
).thenAnswer((_) async => [release]);
|
||||
when(
|
||||
() => codePushClientWrapper.getReleaseArtifact(
|
||||
appId: any(named: 'appId'),
|
||||
releaseId: any(named: 'releaseId'),
|
||||
arch: any(named: 'arch'),
|
||||
platform: any(named: 'platform'),
|
||||
),
|
||||
).thenAnswer((_) async => xcframeworkArtifact);
|
||||
when(
|
||||
() => codePushClientWrapper.publishPatch(
|
||||
appId: any(named: 'appId'),
|
||||
@@ -246,9 +289,18 @@ flutter:
|
||||
supportedOperatingSystems: any(named: 'supportedOperatingSystems'),
|
||||
),
|
||||
).thenAnswer((_) async {});
|
||||
when(
|
||||
() => patchDiffChecker.confirmUnpatchableDiffsIfNecessary(
|
||||
localArtifact: any(named: 'localArtifact'),
|
||||
releaseArtifactUrl: any(named: 'releaseArtifactUrl'),
|
||||
archiveDiffer: archiveDiffer,
|
||||
force: any(named: 'force'),
|
||||
),
|
||||
).thenAnswer((_) async => true);
|
||||
|
||||
command = runWithOverrides(PatchIosFrameworkCommand.new)
|
||||
..testArgResults = argResults;
|
||||
command = runWithOverrides(
|
||||
() => PatchIosFrameworkCommand(archiveDiffer: archiveDiffer),
|
||||
)..testArgResults = argResults;
|
||||
});
|
||||
|
||||
test('has a description', () {
|
||||
@@ -560,6 +612,44 @@ Please re-run the release command for this version or create a new release.'''),
|
||||
expect(exitCode, ExitCode.software.code);
|
||||
});
|
||||
|
||||
test('exits if confirmUnpatchableDiffsIfNecessary returns false', () async {
|
||||
when(() => argResults['force']).thenReturn(false);
|
||||
when(
|
||||
() => patchDiffChecker.confirmUnpatchableDiffsIfNecessary(
|
||||
localArtifact: any(named: 'localArtifact'),
|
||||
releaseArtifactUrl: any(named: 'releaseArtifactUrl'),
|
||||
archiveDiffer: archiveDiffer,
|
||||
force: any(named: 'force'),
|
||||
),
|
||||
).thenAnswer((_) async => false);
|
||||
final tempDir = setUpTempDir();
|
||||
setUpTempArtifacts(tempDir);
|
||||
|
||||
final exitCode = await IOOverrides.runZoned(
|
||||
() => runWithOverrides(command.run),
|
||||
getCurrentDirectory: () => tempDir,
|
||||
);
|
||||
|
||||
expect(exitCode, equals(ExitCode.success.code));
|
||||
verify(
|
||||
() => patchDiffChecker.confirmUnpatchableDiffsIfNecessary(
|
||||
localArtifact: any(named: 'localArtifact'),
|
||||
releaseArtifactUrl: Uri.parse(xcframeworkArtifact.url),
|
||||
archiveDiffer: archiveDiffer,
|
||||
force: false,
|
||||
),
|
||||
).called(1);
|
||||
verifyNever(
|
||||
() => codePushClientWrapper.publishPatch(
|
||||
appId: any(named: 'appId'),
|
||||
releaseId: any(named: 'releaseId'),
|
||||
platform: any(named: 'platform'),
|
||||
channelName: any(named: 'channelName'),
|
||||
patchArtifactBundles: any(named: 'patchArtifactBundles'),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('does not create patch on --dry-run', () async {
|
||||
when(() => argResults['dry-run']).thenReturn(true);
|
||||
final tempDir = setUpTempDir();
|
||||
|
||||
Reference in New Issue
Block a user