emerge 0.7.0 → 0.7.2
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 +4 -4
- data/lib/commands/build/distribution/install.rb +53 -8
- data/lib/commands/reaper/reaper.rb +2 -2
- data/lib/commands/upload/snapshots/snapshots.rb +102 -7
- data/lib/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 65f13db01f807a85200615394b091f5bfea4cd3bf4cf4f94ec0a742d5b93c597
|
4
|
+
data.tar.gz: 322a6582fd941518f080ae8d38af3555a404e295a99658083d5032a0127640d9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6e7adb6bafb19cec3408a3e866d9a148ebe99cf80dde147c47f9133fad0a6d8e7e655a2bfdce7ece4be9596235c650d7abf60ef07f899f30a0a700ef40eccb03
|
7
|
+
data.tar.gz: 9ee165532fb1443fda2f5dce215bb545b9a9997d6684cc5e36d3b18de416a73496779dad9685fac1a03735d327077f040dcd4b17050027abe1accaf7ec1562f8
|
@@ -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 :
|
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[:
|
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[:
|
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
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
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
|
-
|
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 :
|
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[:
|
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)
|
@@ -64,7 +68,7 @@ module EmergeCLI
|
|
64
68
|
|
65
69
|
image_files = @profiler.measure('find_image_files') { find_image_files(client) }
|
66
70
|
|
67
|
-
|
71
|
+
check_duplicate_files(image_files, client)
|
68
72
|
|
69
73
|
run_id = @profiler.measure('create_run') { create_run }
|
70
74
|
|
@@ -126,18 +130,26 @@ module EmergeCLI
|
|
126
130
|
found_images
|
127
131
|
end
|
128
132
|
|
129
|
-
def check_duplicate_files(image_files,
|
133
|
+
def check_duplicate_files(image_files, _client)
|
130
134
|
seen_files = {}
|
135
|
+
duplicate_files = {}
|
136
|
+
|
131
137
|
image_files.each do |image_path|
|
132
|
-
file_name =
|
138
|
+
file_name = File.basename(image_path)
|
133
139
|
|
134
140
|
if seen_files[file_name]
|
135
|
-
|
136
|
-
|
137
|
-
|
141
|
+
duplicate_files[file_name] ||= []
|
142
|
+
duplicate_files[file_name] << image_path
|
143
|
+
else
|
144
|
+
seen_files[file_name] = image_path
|
138
145
|
end
|
139
|
-
seen_files[file_name] = image_path
|
140
146
|
end
|
147
|
+
|
148
|
+
duplicate_files.each do |filename, paths|
|
149
|
+
Logger.warn "Found #{paths.length} duplicate(s) of '#{filename}'. Duplicates: #{paths.join(', ')}"
|
150
|
+
end
|
151
|
+
|
152
|
+
[seen_files, duplicate_files]
|
141
153
|
end
|
142
154
|
|
143
155
|
def create_run
|
@@ -178,6 +190,89 @@ module EmergeCLI
|
|
178
190
|
def upload_images(run_id, concurrency, image_files, client)
|
179
191
|
Logger.info 'Uploading images...'
|
180
192
|
|
193
|
+
if @options[:batch]
|
194
|
+
batch_upload_images(run_id, image_files, client)
|
195
|
+
else
|
196
|
+
individual_upload_images(run_id, concurrency, image_files, client)
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
def batch_upload_images(run_id, image_files, client)
|
201
|
+
Logger.info 'Preparing batch upload...'
|
202
|
+
|
203
|
+
metadata_barrier = Async::Barrier.new
|
204
|
+
metadata_semaphore = Async::Semaphore.new(10, parent: metadata_barrier)
|
205
|
+
|
206
|
+
image_metadata = {
|
207
|
+
manifestVersion: 1,
|
208
|
+
images: {},
|
209
|
+
errors: []
|
210
|
+
}
|
211
|
+
|
212
|
+
used_filenames, = check_duplicate_files(image_files, client)
|
213
|
+
|
214
|
+
@profiler.measure('process_image_metadata') do
|
215
|
+
image_files.each do |image_path|
|
216
|
+
metadata_semaphore.async do
|
217
|
+
file_info = client.parse_file_info(image_path)
|
218
|
+
|
219
|
+
dimensions = @profiler.measure('chunky_png_processing') do
|
220
|
+
datastream = ChunkyPNG::Datastream.from_file(image_path)
|
221
|
+
{
|
222
|
+
width: datastream.header_chunk.width,
|
223
|
+
height: datastream.header_chunk.height
|
224
|
+
}
|
225
|
+
end
|
226
|
+
|
227
|
+
metadata = {
|
228
|
+
fileName: file_info[:file_name],
|
229
|
+
groupName: file_info[:group_name],
|
230
|
+
displayName: file_info[:variant_name],
|
231
|
+
width: dimensions[:width],
|
232
|
+
height: dimensions[:height]
|
233
|
+
}
|
234
|
+
|
235
|
+
image_name = File.basename(image_path, '.*')
|
236
|
+
image_metadata[:images][image_name] = metadata
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
metadata_barrier.wait
|
241
|
+
end
|
242
|
+
|
243
|
+
Tempfile.create(['snapshot_batch', '.zip']) do |zip_file|
|
244
|
+
@profiler.measure('create_zip_file') do
|
245
|
+
Zip::File.open(zip_file.path, Zip::File::CREATE) do |zipfile|
|
246
|
+
zipfile.get_output_stream('manifest.json') { |f| f.write(JSON.generate(image_metadata)) }
|
247
|
+
|
248
|
+
image_files.each do |image_path|
|
249
|
+
filename = File.basename(image_path)
|
250
|
+
# Only add files we haven't seen before
|
251
|
+
zipfile.add(filename, image_path) if used_filenames[filename] == image_path
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
upload_url = @profiler.measure('create_batch_upload_url') do
|
257
|
+
response = @network.post(path: '/v1/snapshots/run/batch-image', body: { run_id: run_id })
|
258
|
+
JSON.parse(response.read).fetch('zip_url')
|
259
|
+
end
|
260
|
+
|
261
|
+
Logger.info 'Uploading images...'
|
262
|
+
Logger.debug "Uploading batch zip file to #{upload_url}"
|
263
|
+
@profiler.measure('upload_batch_zip') do
|
264
|
+
@network.put(
|
265
|
+
path: upload_url,
|
266
|
+
headers: { 'Content-Type' => 'application/zip' },
|
267
|
+
body: File.read(zip_file.path)
|
268
|
+
)
|
269
|
+
end
|
270
|
+
end
|
271
|
+
ensure
|
272
|
+
metadata_barrier&.stop
|
273
|
+
end
|
274
|
+
|
275
|
+
def individual_upload_images(run_id, concurrency, image_files, client)
|
181
276
|
post_image_barrier = Async::Barrier.new
|
182
277
|
post_image_semaphore = Async::Semaphore.new(concurrency, parent: post_image_barrier)
|
183
278
|
|
data/lib/version.rb
CHANGED
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.
|
4
|
+
version: 0.7.2
|
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-
|
11
|
+
date: 2025-02-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: async-http
|