zipProject static method

Future<List<int>> zipProject({
  1. required CommandLogger logger,
  2. required Directory rootDirectory,
  3. Iterable<String> beneath = const ['.'],
  4. int fileReadPoolSize = 5,
})

Zips a project directory. Returns a list of bytes representing the zipped project.

The logger is used to log debug information and warnings. The rootDirectory is the directory under which contents will be zipped. The beneath is the list of relative paths under rootDirectory that will be included, all by default. The fileReadPoolSize is the number of files that are processed concurrently.

All exceptions thrown by this method are subclasses of ProjectZipperExceptions. Throws ProjectDirectoryDoesNotExistException if the project directory does not exist. Throws EmptyProjectException if the project directory is empty. Throws DirectorySymLinkException if the project directory contains a directory symlink. Throws NonResolvingSymlinkException if the project directory contains a non-resolving symlink.

Implementation

static Future<List<int>> zipProject({
  required final CommandLogger logger,
  required final Directory rootDirectory,
  final Iterable<String> beneath = const ['.'],
  final int fileReadPoolSize = 5,
}) async {
  final projectPath = rootDirectory.path;

  if (!rootDirectory.existsSync()) {
    throw ProjectDirectoryDoesNotExistException(projectPath);
  }

  final filesToUpload = <String>{};
  final filesIgnored = <String>{};
  for (final b in beneath) {
    final (included, ignored) = ProjectFiles.collectFiles(
      logger: logger,
      rootDirectory: rootDirectory,
      beneath: b,
    );
    filesToUpload.addAll(included);
    filesIgnored.addAll(ignored);
  }

  logger.debug('Found ${filesToUpload.length} files to upload.');
  if (logger.logLevel == LogLevel.debug) {
    FileTreePrinter.writeFileTree(
      filePaths: filesToUpload
          .map((final file) => stripRoot(projectPath, file))
          .toSet(),
      ignoredPaths: filesIgnored
          .map((final file) => stripRoot(projectPath, file))
          .toSet(),
      write: logger.raw,
    );
  }

  final archive = Archive();
  final fileReadPool = Pool(fileReadPoolSize);

  Future<void> addFileToArchive(final String path) async {
    final file = File(path);
    if (!file.existsSync()) return;

    await fileReadPool.withResource(() async {
      final length = await file.length();
      final bytes = await file.readAsBytes();

      archive.addFile(
        ArchiveFile(stripRoot(projectPath, path), length, bytes),
      );
    });
  }

  await Future.wait(filesToUpload.map(addFileToArchive));

  if (archive.isEmpty) {
    throw const EmptyProjectException();
  }

  final encoded = ZipEncoder().encode(archive);
  logger.debug(
    'Encoded ${archive.length} files to ${_formatFileSize(encoded?.length ?? 0)}.',
  );

  if (encoded == null) {
    // This should never happen.
    // If we end up here, it's a bug in the archive package.
    throw const NullZipException();
  }

  return encoded;
}