# typed: strict # frozen_string_literal: true require "system_command" module UnpackStrategy class Zip module MacOSZipExtension extend T::Sig include UnpackStrategy include SystemCommand::Mixin using Magic sig { override.params(unpack_dir: Pathname, basename: Pathname, verbose: T::Boolean).returns(T.untyped) } def extract_to_dir(unpack_dir, basename:, verbose:) with_env(TZ: "UTC") do if merge_xattrs && contains_extended_attributes?(path) # We use ditto directly, because dot_clean has issues if the __MACOSX # folder has incorrect permissions. # (Also, Homebrew's ZIP artifact automatically deletes this folder.) return system_command! "ditto", args: ["-x", "-k", path, unpack_dir], verbose: verbose, print_stderr: false end result = begin T.let(super, T.nilable(SystemCommand::Result)) rescue ErrorDuringExecution => e raise unless e.stderr.include?("End-of-central-directory signature not found.") system_command! "ditto", args: ["-x", "-k", path, unpack_dir], verbose: verbose nil end return if result.blank? volumes = result.stderr.chomp .split("\n") .map { |l| l[/\A skipping: (.+) volume label\Z/, 1] } .compact return if volumes.empty? Dir.mktmpdir do |tmp_unpack_dir| tmp_unpack_dir = Pathname(tmp_unpack_dir) # `ditto` keeps Finder attributes intact and does not skip volume labels # like `unzip` does, which can prevent disk images from being unzipped. system_command! "ditto", args: ["-x", "-k", path, tmp_unpack_dir], verbose: verbose volumes.each do |volume| FileUtils.mv tmp_unpack_dir/volume, unpack_dir/volume, verbose: verbose end end end end private sig { params(path: Pathname).returns(T::Boolean) } def contains_extended_attributes?(path) path.zipinfo.grep(/(^__MACOSX|\._)/).any? end end private_constant :MacOSZipExtension prepend MacOSZipExtension end end