diff --git a/lib/morph-cli.rb b/lib/morph-cli.rb index ddadb4d..9fc0bd4 100644 --- a/lib/morph-cli.rb +++ b/lib/morph-cli.rb @@ -6,6 +6,7 @@ require 'tempfile' require 'fileutils' require 'filesize' +require 'zlib' require 'faraday' require 'faraday/multipart' require 'minitar' @@ -42,7 +43,7 @@ def self.execute(directory, _development, env_config) connection.post("/run") do |req| req.body = { api_key: env_config[:api_key], - code: Faraday::Multipart::FilePart.new(file, "application/octet-stream") + code: Faraday::Multipart::FilePart.new(file, "application/gzip") } req.options.timeout = timeout req.options.on_data = proc do |chunk, _overall_received_bytes, env| @@ -106,21 +107,22 @@ def self.in_directory(directory) FileUtils.cd(cwd) end - # Packs the given paths (relative to directory) into a tar file and returns - # an open, rewound file handle ready for upload. + # Packs the given paths (relative to directory) into a gzip-compressed tar + # file and returns an open, rewound file handle ready for upload. def self.create_tar(directory, paths) - tempfile = Tempfile.new(["morph", ".tar"]) + tempfile = Tempfile.new(["morph", ".tar.gz"]) tempfile.binmode in_directory(directory) do - output = Minitar::Output.new(tempfile) - paths.each do |entry| - Minitar.pack_file(entry, output) - end + gzip = Zlib::GzipWriter.new(tempfile) + output = Minitar::Output.new(gzip) + paths.each { |entry| Minitar.pack_file(entry, output) } ensure - # Writes the tar trailer and flushes without closing the underlying - # tempfile, so the returned handle stays open for reading. + # Closing the tar writer writes the tar trailer; finishing (not + # closing) the gzip stream writes the gzip trailer, leaving the + # underlying tempfile handle open for reading. output&.tar&.close + gzip&.finish end tempfile.flush diff --git a/spec/morph_cli_spec.rb b/spec/morph_cli_spec.rb index 66111de..79b25f5 100644 --- a/spec/morph_cli_spec.rb +++ b/spec/morph_cli_spec.rb @@ -30,7 +30,7 @@ def with_scraper_directory end end - it "posts the API key and the code as multipart form data" do + it "posts the API key and the gzipped code as multipart form data" do stub_request(:post, "https://morph.io/run").to_return(status: 200, body: "") with_scraper_directory do |dir| @@ -41,7 +41,7 @@ def with_scraper_directory expect(WebMock).to(have_requested(:post, "https://morph.io/run").with do |req| req.headers["Content-Type"].start_with?("multipart/form-data") && req.body.include?("secret-key") && - req.body.include?("scraper.rb") + req.body.b.include?("\x1f\x8b".b) # gzip magic bytes end) end @@ -122,7 +122,7 @@ def with_scraper_directory end describe ".create_tar" do - it "packs the given paths into a readable tar" do + it "packs the given paths into a readable gzip-compressed tar" do Dir.mktmpdir do |dir| File.write(File.join(dir, "scraper.rb"), "puts 'hi'\n") FileUtils.mkdir_p(File.join(dir, "lib")) @@ -132,13 +132,25 @@ def with_scraper_directory tar = described_class.create_tar(dir, paths) names = [] - Minitar::Input.open(tar.path) do |input| - input.each { |entry| names << entry.full_name } + Zlib::GzipReader.open(tar.path) do |gzip| + Minitar::Input.open(gzip) do |input| + input.each { |entry| names << entry.full_name } + end end expect(names).to contain_exactly("scraper.rb", "lib/helper.rb") end end + it "compresses the tar" do + Dir.mktmpdir do |dir| + File.write(File.join(dir, "scraper.rb"), "a" * 100_000) + + tar = described_class.create_tar(dir, described_class.all_paths(dir)) + + expect(File.size(tar.path)).to be < 100_000 + end + end + it "returns an open file handle ready for reading" do Dir.mktmpdir do |dir| File.write(File.join(dir, "scraper.rb"), "puts 'hi'\n") @@ -147,7 +159,7 @@ def with_scraper_directory expect(tar).not_to be_closed expect(tar.pos).to eq(0) - expect(tar.read).to include("scraper.rb") + expect(tar.read(2)).to eq("\x1f\x8b".b) # gzip magic bytes end end end