findArtifact static method

Future<String> findArtifact(
  1. String projectRoot, {
  2. String aabOutputPath = 'build/app/outputs/bundle/release',
})

Finds exactly one repository-contained Android App Bundle.

Implementation

static Future<String> findArtifact(
  String projectRoot, {
  String aabOutputPath = 'build/app/outputs/bundle/release',
}) async {
  final normalizedRoot = p.normalize(p.absolute(projectRoot));
  final configuredPath = p.normalize(
    p.absolute(normalizedRoot, aabOutputPath),
  );
  SmfError.check(
    p.equals(configuredPath, normalizedRoot) || p.isWithin(normalizedRoot, configuredPath),
    'The configured aab_output_path must stay inside the Flutter app.',
    SmfErrorCode.aabPathEscape,
  );
  final type = await FileSystemEntity.type(configuredPath);
  SmfError.check(
    type != FileSystemEntityType.notFound,
    'The build command did not produce an AAB at $configuredPath.',
    SmfErrorCode.aabNotFound,
  );
  final realRoot = await Directory(normalizedRoot).resolveSymbolicLinks();
  final realConfiguredPath = await switch (type) {
    FileSystemEntityType.file => File(configuredPath).resolveSymbolicLinks(),
    FileSystemEntityType.directory => Directory(
      configuredPath,
    ).resolveSymbolicLinks(),
    _ => throw const SmfError(
      'The configured aab_output_path must be a file or directory.',
      SmfErrorCode.aabNotFound,
    ),
  };
  SmfError.check(
    p.equals(realConfiguredPath, realRoot) || p.isWithin(realRoot, realConfiguredPath),
    'The configured aab_output_path resolves outside the Flutter app.',
    SmfErrorCode.aabPathEscape,
  );
  if (type == FileSystemEntityType.file) {
    SmfError.check(
      configuredPath.toLowerCase().endsWith('.aab'),
      'The configured Android artifact file must have an .aab extension.',
      SmfErrorCode.aabNotFound,
    );
    return configuredPath;
  }

  final artifactPaths = <String>[];
  await for (final entity in Directory(configuredPath).list(recursive: true)) {
    if (entity is! File || !entity.path.toLowerCase().endsWith('.aab')) {
      continue;
    }
    final realFile = await entity.resolveSymbolicLinks();
    SmfError.check(
      p.isWithin(realRoot, realFile),
      'An AAB in aab_output_path resolves outside the Flutter app.',
      SmfErrorCode.aabPathEscape,
    );
    artifactPaths.add(entity.path);
  }
  artifactPaths.sort();
  SmfError.check(
    artifactPaths.length == 1,
    'Expected exactly one AAB in $configuredPath, found ${artifactPaths.length}.',
    SmfErrorCode.aabCount,
  );
  return artifactPaths.single;
}