emerge 0.7.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f526af7bee28fe19586eb432339f8b6d771e98c9127a5126a4be9f47a32d6ab7
4
- data.tar.gz: eb356b6d63d1e66595550516aa78c36d7926b867ac5caf9a35000f0c8b3feeb5
3
+ metadata.gz: 409fec8581e499747b3c9ca5cd80e0f9c9e491ca0d94688bc1e0d299a974a95b
4
+ data.tar.gz: 64863c523c84b25f5ff21a9bbda0863f13ef7a5165d5437aeb66d60211e7fe84
5
5
  SHA512:
6
- metadata.gz: 5e3ba42f1367adf1b50e0bb236b79dc58225df5bd29e7e3af419775cac52e2d04415d5c949b0a62f2eb17f0ae5d25bd82cc086f194d34da0f912dd35af0e433a
7
- data.tar.gz: adf35a55cf1e66d9b0de879e8bab9843c2da10d3a00784dc2eebab7f865ac6ca501be7f81a28c660c6de60ff6ffb4f8e0670fbaeaf4057a67c943424759c8855
6
+ metadata.gz: 5a6dc2be9e2ad1f3078c9f6703d35c2f1e70d5630d14a2f682216fb949c1111b7bc236702e2e9698714f4662e5e7a70bcb67118c510542b61d1b58c00f13dc1e
7
+ data.tar.gz: 76f622ef6797a7c420d1406e9f43ddceb9f075180641ae6e5b78cbb63a3a18489d883d63346d973f9ff8e6b225281d2d0671e6ad33d5ff667b5c7d347c8ba769
@@ -3,6 +3,7 @@ require 'cfpropertylist'
3
3
  require 'zip'
4
4
  require 'rbconfig'
5
5
  require 'tmpdir'
6
+ require 'tty-prompt'
6
7
 
7
8
  module EmergeCLI
8
9
  module Commands
@@ -13,7 +14,7 @@ module EmergeCLI
13
14
 
14
15
  option :api_token, type: :string, required: false,
15
16
  desc: 'API token for authentication, defaults to ENV[EMERGE_API_TOKEN]'
16
- option :build_id, type: :string, required: true, desc: 'Build ID to download'
17
+ option :id, type: :string, required: true, desc: 'Emerge build ID to download'
17
18
  option :install, type: :boolean, default: true, required: false, desc: 'Install the build on the device'
18
19
  option :device_id, type: :string, desc: 'Specific device ID to target'
19
20
  option :device_type, type: :string, enum: %w[virtual physical any], default: 'any',
@@ -32,7 +33,7 @@ module EmergeCLI
32
33
  api_token = @options[:api_token] || ENV.fetch('EMERGE_API_TOKEN', nil)
33
34
  raise 'API token is required' unless api_token
34
35
 
35
- raise 'Build ID is required' unless @options[:build_id]
36
+ raise 'Build ID is required' unless @options[:id]
36
37
 
37
38
  output_name = nil
38
39
  app_id = nil
@@ -41,7 +42,7 @@ module EmergeCLI
41
42
  @network ||= EmergeCLI::Network.new(api_token:)
42
43
 
43
44
  Logger.info 'Getting build URL...'
44
- request = get_build_url(@options[:build_id])
45
+ request = get_build_url(@options[:id])
45
46
  response = parse_response(request)
46
47
 
47
48
  platform = response['platform']
@@ -49,10 +50,32 @@ module EmergeCLI
49
50
  app_id = response['appId']
50
51
 
51
52
  extension = platform == 'ios' ? 'ipa' : 'apk'
52
- Logger.info 'Downloading build...'
53
- output_name = @options[:output] || "#{@options[:build_id]}.#{extension}"
54
- `curl --progress-bar -L '#{download_url}' -o #{output_name} `
55
- Logger.info "Build downloaded to #{output_name}"
53
+ output_name = @options[:output] || "#{@options[:id]}.#{extension}"
54
+
55
+ if File.exist?(output_name)
56
+ Logger.info "Build file already exists at #{output_name}"
57
+ prompt = TTY::Prompt.new
58
+ choice = prompt.select('What would you like to do?', {
59
+ 'Install existing file' => :install,
60
+ 'Overwrite with new download' => :overwrite,
61
+ 'Cancel' => :cancel
62
+ })
63
+
64
+ case choice
65
+ when :install
66
+ Logger.info 'Proceeding with existing file...'
67
+ when :overwrite
68
+ Logger.info 'Downloading new build...'
69
+ `curl --progress-bar -L '#{download_url}' -o #{output_name}`
70
+ Logger.info "✅ Build downloaded to #{output_name}"
71
+ when :cancel
72
+ raise 'Operation cancelled by user'
73
+ end
74
+ else
75
+ Logger.info 'Downloading build...'
76
+ `curl --progress-bar -L '#{download_url}' -o #{output_name}`
77
+ Logger.info "✅ Build downloaded to #{output_name}"
78
+ end
56
79
  rescue StandardError => e
57
80
  Logger.error "❌ Failed to download build: #{e.message}"
58
81
  raise e
@@ -128,12 +151,34 @@ module EmergeCLI
128
151
  end
129
152
 
130
153
  def install_android_build(build_path)
131
- command = "adb -s #{@options[:device_id]} install #{build_path}"
154
+ device_id = @options[:device_id] || select_android_device
155
+ raise 'No Android devices found' unless device_id
156
+
157
+ command = "adb -s #{device_id} install #{build_path}"
132
158
  Logger.debug "Running command: #{command}"
133
159
  `#{command}`
134
160
 
135
161
  Logger.info '✅ Build installed'
136
162
  end
163
+
164
+ def select_android_device
165
+ devices = get_android_devices
166
+ return nil if devices.empty?
167
+ return devices.first if devices.length == 1
168
+
169
+ prompt = TTY::Prompt.new
170
+ Logger.info 'Multiple Android devices found.'
171
+ prompt.select('Choose a device:', devices)
172
+ end
173
+
174
+ def get_android_devices
175
+ output = `adb devices`
176
+ # Split output into lines, remove first line (header), and extract device IDs
177
+ output.split("\n")[1..]
178
+ .map(&:strip)
179
+ .reject(&:empty?)
180
+ .map { |line| line.split("\t").first }
181
+ end
137
182
  end
138
183
  end
139
184
  end
@@ -7,7 +7,7 @@ module EmergeCLI
7
7
  class Reaper < EmergeCLI::Commands::GlobalOptions
8
8
  desc 'Analyze dead code from an Emerge upload'
9
9
 
10
- option :upload_id, type: :string, required: true, desc: 'Upload ID to analyze'
10
+ option :id, type: :string, required: true, desc: 'Emerge build ID to analyze'
11
11
  option :project_root, type: :string, required: true,
12
12
  desc: 'Root directory of the project, defaults to current directory'
13
13
 
@@ -38,7 +38,7 @@ module EmergeCLI
38
38
  project_root = @options[:project_root] || Dir.pwd
39
39
 
40
40
  Sync do
41
- all_data = @profiler.measure('fetch_dead_code') { fetch_all_dead_code(@options[:upload_id]) }
41
+ all_data = @profiler.measure('fetch_dead_code') { fetch_all_dead_code(@options[:id]) }
42
42
  result = @profiler.measure('parse_dead_code') { DeadCodeResult.new(all_data) }
43
43
 
44
44
  Logger.info result.to_s
@@ -6,6 +6,8 @@ require 'async'
6
6
  require 'async/barrier'
7
7
  require 'async/semaphore'
8
8
  require 'async/http/internet/instance'
9
+ require 'zip'
10
+ require 'tempfile'
9
11
 
10
12
  module EmergeCLI
11
13
  module Commands
@@ -33,6 +35,8 @@ module EmergeCLI
33
35
 
34
36
  option :profile, type: :boolean, default: false, desc: 'Enable performance profiling metrics'
35
37
 
38
+ option :batch, type: :boolean, default: false, desc: 'Upload images in batch using zip file'
39
+
36
40
  argument :image_paths, type: :array, required: false, desc: 'Paths to folders containing images'
37
41
 
38
42
  def initialize(network: nil, git_info_provider: nil)
@@ -178,6 +182,86 @@ module EmergeCLI
178
182
  def upload_images(run_id, concurrency, image_files, client)
179
183
  Logger.info 'Uploading images...'
180
184
 
185
+ if @options[:batch]
186
+ batch_upload_images(run_id, image_files, client)
187
+ else
188
+ individual_upload_images(run_id, concurrency, image_files, client)
189
+ end
190
+ end
191
+
192
+ def batch_upload_images(run_id, image_files, client)
193
+ Logger.info 'Preparing batch upload...'
194
+
195
+ metadata_barrier = Async::Barrier.new
196
+ metadata_semaphore = Async::Semaphore.new(10, parent: metadata_barrier)
197
+
198
+ image_metadata = {
199
+ manifestVersion: 1,
200
+ images: {},
201
+ errors: []
202
+ }
203
+
204
+ @profiler.measure('process_image_metadata') do
205
+ image_files.each do |image_path|
206
+ metadata_semaphore.async do
207
+ file_info = client.parse_file_info(image_path)
208
+
209
+ dimensions = @profiler.measure('chunky_png_processing') do
210
+ datastream = ChunkyPNG::Datastream.from_file(image_path)
211
+ {
212
+ width: datastream.header_chunk.width,
213
+ height: datastream.header_chunk.height
214
+ }
215
+ end
216
+
217
+ metadata = {
218
+ fileName: file_info[:file_name],
219
+ groupName: file_info[:group_name],
220
+ displayName: file_info[:variant_name],
221
+ width: dimensions[:width],
222
+ height: dimensions[:height]
223
+ }
224
+
225
+ image_name = File.basename(image_path, '.*')
226
+ image_metadata[:images][image_name] = metadata
227
+ end
228
+ end
229
+
230
+ metadata_barrier.wait
231
+ end
232
+
233
+ Tempfile.create(['snapshot_batch', '.zip']) do |zip_file|
234
+ @profiler.measure('create_zip_file') do
235
+ Zip::File.open(zip_file.path, Zip::File::CREATE) do |zipfile|
236
+ zipfile.get_output_stream('manifest.json') { |f| f.write(JSON.generate(image_metadata)) }
237
+
238
+ image_files.each do |image_path|
239
+ image_name = File.basename(image_path)
240
+ zipfile.add(image_name, image_path)
241
+ end
242
+ end
243
+ end
244
+
245
+ upload_url = @profiler.measure('create_batch_upload_url') do
246
+ response = @network.post(path: '/v1/snapshots/run/batch-image', body: { run_id: run_id })
247
+ JSON.parse(response.read).fetch('zip_url')
248
+ end
249
+
250
+ Logger.info 'Uploading images...'
251
+ Logger.debug "Uploading batch zip file to #{upload_url}"
252
+ @profiler.measure('upload_batch_zip') do
253
+ @network.put(
254
+ path: upload_url,
255
+ headers: { 'Content-Type' => 'application/zip' },
256
+ body: File.read(zip_file.path)
257
+ )
258
+ end
259
+ end
260
+ ensure
261
+ metadata_barrier&.stop
262
+ end
263
+
264
+ def individual_upload_images(run_id, concurrency, image_files, client)
181
265
  post_image_barrier = Async::Barrier.new
182
266
  post_image_semaphore = Async::Semaphore.new(concurrency, parent: post_image_barrier)
183
267
 
data/lib/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module EmergeCLI
2
- VERSION = '0.7.0'.freeze
2
+ VERSION = '0.7.1'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: emerge
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.7.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Emerge Tools
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-01-31 00:00:00.000000000 Z
11
+ date: 2025-02-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async-http