emerge 0.5.0 → 0.6.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: 1ebd308782a251e11a4d515c0728b572456ed730ef2376f634939fec6ace820e
4
- data.tar.gz: d476865caa4a62c32d9b258ec6fbfb474cb67c7bdf05c3ef1548ff860a1a9697
3
+ metadata.gz: 1bc78d26069b4e3ef6c4154958eab2ba668ef0e2074d10bfca920dea1767d742
4
+ data.tar.gz: 6820120287c28ff285e819a5416c7db6568331664e138402d5b03660e1913dcd
5
5
  SHA512:
6
- metadata.gz: 149980818e3fcb6741c029a601cb903dcd05051a4564889d41b8e49c4380a7f08662b26cbfe5eca0e3ced6b114a12075118fd749e88ccb27ec694a4352799f8d
7
- data.tar.gz: d1ed903d5c8560a649fd4566a45067403fe506112d79ff6e686763bfee02d8c7c9a2823fc7a23fc5af4cf93f5c6a20fb82d59644fd3347b837256df43b09abae
6
+ metadata.gz: 699e8dfac1ad70f312261ccd34790185e1d0f72ca84b25574470c1b312870da04add5f3c6ce3f51a89840ce21d2713ba27641552345960608dc991c61bf005a1
7
+ data.tar.gz: 9094c02b3add07de9fe9a0309a55cf133ea9f4f77d0ef1064ce15703e366a940987ddf39d21f8957d2e41dfacf2021d98dd9c89210b8fe49f8c88c2f8d3f8d13
@@ -2,6 +2,7 @@ require 'dry/cli'
2
2
  require 'cfpropertylist'
3
3
  require 'zip'
4
4
  require 'rbconfig'
5
+ require 'tmpdir'
5
6
 
6
7
  module EmergeCLI
7
8
  module Commands
@@ -13,7 +14,9 @@ module EmergeCLI
13
14
  desc: 'API token for authentication, defaults to ENV[EMERGE_API_TOKEN]'
14
15
  option :build_id, type: :string, required: true, desc: 'Build ID to download'
15
16
  option :install, type: :boolean, default: true, required: false, desc: 'Install the build on the device'
16
- option :device_id, type: :string, required: false, desc: 'Device id to install the build'
17
+ option :device_id, type: :string, desc: 'Specific device ID to target'
18
+ option :device_type, type: :string, enum: %w[virtual physical any], default: 'any',
19
+ desc: 'Type of device to target (virtual/physical/any)'
17
20
  option :output, type: :string, required: false, desc: 'Output path for the downloaded build'
18
21
 
19
22
  def initialize(network: nil)
@@ -30,6 +33,9 @@ module EmergeCLI
30
33
 
31
34
  raise 'Build ID is required' unless @options[:build_id]
32
35
 
36
+ output_name = nil
37
+ app_id = nil
38
+
33
39
  begin
34
40
  @network ||= EmergeCLI::Network.new(api_token:)
35
41
 
@@ -39,24 +45,32 @@ module EmergeCLI
39
45
 
40
46
  platform = response['platform']
41
47
  download_url = response['downloadUrl']
48
+ app_id = response['appId']
42
49
 
43
50
  extension = platform == 'ios' ? 'ipa' : 'apk'
44
51
  Logger.info 'Downloading build...'
45
52
  output_name = @options[:output] || "#{@options[:build_id]}.#{extension}"
46
53
  `curl --progress-bar -L '#{download_url}' -o #{output_name} `
47
54
  Logger.info "✅ Build downloaded to #{output_name}"
48
-
49
- if @options[:install]
50
- install_ios_build(output_name) if platform == 'ios'
51
- install_android_build(output_name) if platform == 'android'
52
- end
53
55
  rescue StandardError => e
54
- Logger.error "Failed to download build: #{e.message}"
55
- Logger.error 'Check your parameters and try again'
56
+ Logger.error "Failed to download build: #{e.message}"
56
57
  raise e
57
58
  ensure
58
59
  @network&.close
59
60
  end
61
+
62
+ begin
63
+ if @options[:install] && !output_name.nil?
64
+ if platform == 'ios'
65
+ install_ios_build(output_name, app_id)
66
+ elsif platform == 'android'
67
+ install_android_build(output_name)
68
+ end
69
+ end
70
+ rescue StandardError => e
71
+ Logger.error "❌ Failed to install build: #{e.message}"
72
+ raise e
73
+ end
60
74
  end
61
75
  end
62
76
 
@@ -86,12 +100,30 @@ module EmergeCLI
86
100
  end
87
101
  end
88
102
 
89
- def install_ios_build(build_path)
90
- command = "xcrun devicectl device install app -d #{@options[:device_id]} #{build_path}"
91
- Logger.debug "Running command: #{command}"
92
- `#{command}`
93
-
103
+ def install_ios_build(build_path, app_id)
104
+ device_type = case @options[:device_type]
105
+ when 'simulator'
106
+ XcodeDeviceManager::DeviceType::VIRTUAL
107
+ when 'physical'
108
+ XcodeDeviceManager::DeviceType::PHYSICAL
109
+ else
110
+ XcodeDeviceManager::DeviceType::ANY
111
+ end
112
+
113
+ device_manager = XcodeDeviceManager.new
114
+ device = if @options[:device_id]
115
+ device_manager.find_device_by_id(@options[:device_id])
116
+ else
117
+ device_manager.find_device_by_type(device_type, build_path)
118
+ end
119
+
120
+ Logger.info "Installing build on #{device.device_id}"
121
+ device.install_app(build_path)
94
122
  Logger.info '✅ Build installed'
123
+
124
+ Logger.info "Launching app #{app_id}..."
125
+ device.launch_app(app_id)
126
+ Logger.info '✅ Build launched'
95
127
  end
96
128
 
97
129
  def install_android_build(build_path)
data/lib/emerge_cli.rb CHANGED
@@ -24,6 +24,7 @@ require_relative 'commands/autofixes/exported_symbols'
24
24
  require_relative 'reaper/ast_parser'
25
25
  require_relative 'reaper/code_deleter'
26
26
 
27
+ require_relative 'utils/environment'
27
28
  require_relative 'utils/git_info_provider'
28
29
  require_relative 'utils/git_result'
29
30
  require_relative 'utils/github'
@@ -34,6 +35,9 @@ require_relative 'utils/profiler'
34
35
  require_relative 'utils/project_detector'
35
36
  require_relative 'utils/macho_parser'
36
37
  require_relative 'utils/version_check'
38
+ require_relative 'utils/xcode_device_manager'
39
+ require_relative 'utils/xcode_simulator'
40
+ require_relative 'utils/xcode_physical_device'
37
41
 
38
42
  require 'dry/cli'
39
43
 
@@ -0,0 +1,7 @@
1
+ module EmergeCLI
2
+ class Environment
3
+ def execute_command(command)
4
+ `#{command}`
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,158 @@
1
+ require 'json'
2
+ require_relative 'xcode_simulator'
3
+ require 'zip'
4
+ require 'cfpropertylist'
5
+
6
+ module EmergeCLI
7
+ class XcodeDeviceManager
8
+ class DeviceType
9
+ VIRTUAL = :virtual
10
+ PHYSICAL = :physical
11
+ ANY = :any
12
+ end
13
+
14
+ def initialize(environment: Environment.new)
15
+ @environment = environment
16
+ end
17
+
18
+ class << self
19
+ def get_supported_platforms(ipa_path)
20
+ return [] unless ipa_path&.end_with?('.ipa')
21
+
22
+ Zip::File.open(ipa_path) do |zip_file|
23
+ app_entry = zip_file.glob('**/*.app/').first ||
24
+ zip_file.glob('**/*.app').first ||
25
+ zip_file.find { |entry| entry.name.end_with?('.app/') || entry.name.end_with?('.app') }
26
+
27
+ raise 'No .app found in .ipa file' unless app_entry
28
+
29
+ app_dir = app_entry.name.end_with?('/') ? app_entry.name.chomp('/') : app_entry.name
30
+ info_plist_path = "#{app_dir}/Info.plist"
31
+ info_plist_entry = zip_file.find_entry(info_plist_path)
32
+ raise 'Info.plist not found in app bundle' unless info_plist_entry
33
+
34
+ info_plist_content = info_plist_entry.get_input_stream.read
35
+ plist = CFPropertyList::List.new(data: info_plist_content)
36
+ info_plist = CFPropertyList.native_types(plist.value)
37
+
38
+ info_plist['CFBundleSupportedPlatforms'] || []
39
+ end
40
+ end
41
+ end
42
+
43
+ def find_device_by_id(device_id)
44
+ Logger.debug "Looking for device with ID: #{device_id}"
45
+ devices_json = execute_command('xcrun xcdevice list')
46
+ devices_data = JSON.parse(devices_json)
47
+
48
+ found_device = devices_data.find { |device| device['identifier'] == device_id }
49
+ raise "No device found with ID: #{device_id}" unless found_device
50
+
51
+ device_type = found_device['simulator'] ? 'simulator' : 'physical'
52
+ Logger.info "✅ Found device: #{found_device['name']} " \
53
+ "(#{found_device['identifier']}, #{device_type})"
54
+ if found_device['simulator']
55
+ XcodeSimulator.new(found_device['identifier'])
56
+ else
57
+ XcodePhysicalDevice.new(found_device['identifier'])
58
+ end
59
+ end
60
+
61
+ def find_device_by_type(device_type, ipa_path)
62
+ case device_type
63
+ when DeviceType::VIRTUAL
64
+ find_and_boot_most_recently_used_simulator
65
+ when DeviceType::PHYSICAL
66
+ find_connected_device
67
+ when DeviceType::ANY
68
+ # Check supported platforms in Info.plist to make intelligent choice
69
+ supported_platforms = self.class.get_supported_platforms(ipa_path)
70
+ Logger.debug "Build supports platforms: #{supported_platforms.join(', ')}"
71
+
72
+ if supported_platforms.include?('iPhoneOS')
73
+ device = find_connected_device
74
+ return device if device
75
+
76
+ # Only fall back to simulator if it's also supported
77
+ unless supported_platforms.include?('iPhoneSimulator')
78
+ raise 'Build only supports physical devices, but no device is connected'
79
+ end
80
+ Logger.info 'No physical device found, falling back to simulator since build supports both'
81
+ find_and_boot_most_recently_used_simulator
82
+
83
+ elsif supported_platforms.include?('iPhoneSimulator')
84
+ find_and_boot_most_recently_used_simulator
85
+ else
86
+ raise "Build doesn't support either physical devices or simulators"
87
+ end
88
+ end
89
+ end
90
+
91
+ private
92
+
93
+ def execute_command(command)
94
+ @environment.execute_command(command)
95
+ end
96
+
97
+ def find_connected_device
98
+ Logger.info 'Finding connected device...'
99
+ devices_json = execute_command('xcrun xcdevice list')
100
+ Logger.debug "Device list output: #{devices_json}"
101
+
102
+ devices_data = JSON.parse(devices_json)
103
+ physical_devices = devices_data
104
+ .select do |device|
105
+ device['simulator'] == false &&
106
+ device['ignored'] == false &&
107
+ device['available'] == true &&
108
+ device['platform'] == 'com.apple.platform.iphoneos'
109
+ end
110
+
111
+ Logger.debug "Found physical devices: #{physical_devices}"
112
+
113
+ if physical_devices.empty?
114
+ Logger.info 'No physical connected device found'
115
+ return nil
116
+ end
117
+
118
+ device = physical_devices.first
119
+ Logger.info "Found connected physical device: #{device['name']} (#{device['identifier']})"
120
+ XcodePhysicalDevice.new(device['identifier'])
121
+ end
122
+
123
+ def find_and_boot_most_recently_used_simulator
124
+ Logger.info 'Finding and booting most recently used simulator...'
125
+ simulators_json = execute_command('xcrun simctl list devices --json')
126
+ Logger.debug "Simulators JSON: #{simulators_json}"
127
+
128
+ simulators_data = JSON.parse(simulators_json)
129
+
130
+ simulators = simulators_data['devices'].flat_map do |runtime, devices|
131
+ next [] unless runtime.include?('iOS') # Only include iOS devices
132
+
133
+ devices.select do |device|
134
+ (device['name'].start_with?('iPhone', 'iPad') &&
135
+ device['isAvailable'] &&
136
+ !device['isDeleted'])
137
+ end.map do |device|
138
+ version = runtime.match(/iOS-(\d+)-(\d+)/)&.captures&.join('.').to_f
139
+ last_booted = device['lastBootedAt'] ? Time.parse(device['lastBootedAt']) : Time.at(0)
140
+ [device['udid'], device['state'], version, last_booted]
141
+ end
142
+ end.sort_by { |_, _, _, last_booted| last_booted }.reverse
143
+
144
+ Logger.debug "Simulators: #{simulators}"
145
+
146
+ raise 'No available simulator found' unless simulators.any?
147
+
148
+ simulator_id, simulator_state, version, last_booted = simulators.first
149
+ version_str = version.zero? ? '' : " (#{version})"
150
+ last_booted_str = last_booted == Time.at(0) ? 'never' : last_booted.strftime('%Y-%m-%d %H:%M:%S')
151
+ Logger.info "Found simulator #{simulator_id}#{version_str} (#{simulator_state}, last booted: #{last_booted_str})"
152
+
153
+ simulator = XcodeSimulator.new(simulator_id, environment: @environment)
154
+ simulator.boot unless simulator_state == 'Booted'
155
+ simulator
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,108 @@
1
+ require 'English'
2
+ require 'timeout'
3
+ require 'zip'
4
+ require 'cfpropertylist'
5
+ require 'fileutils'
6
+
7
+ module EmergeCLI
8
+ class XcodePhysicalDevice
9
+ attr_reader :device_id
10
+
11
+ def initialize(device_id)
12
+ @device_id = device_id
13
+ end
14
+
15
+ def install_app(ipa_path)
16
+ raise "Non-IPA file provided: #{ipa_path}" unless ipa_path.end_with?('.ipa')
17
+
18
+ Logger.info "Installing app to device #{@device_id}..."
19
+
20
+ begin
21
+ # Set a timeout since I've noticed xcrun devicectl can occasionally hang for invalid apps
22
+ Timeout.timeout(60) do
23
+ command = "xcrun devicectl device install app --device #{@device_id} \"#{ipa_path}\""
24
+ Logger.debug "Running command: #{command}"
25
+
26
+ output = `#{command} 2>&1`
27
+ Logger.debug "Install command output: #{output}"
28
+
29
+ if output.include?('ERROR:') || output.include?('error:')
30
+ if output.include?('This provisioning profile cannot be installed on this device')
31
+ bundle_id = extract_bundle_id_from_error(output)
32
+ raise "Failed to install app: The provisioning profile for #{bundle_id} is not " \
33
+ "valid for this device. Make sure the device's UDID is included in the " \
34
+ 'provisioning profile.'
35
+ elsif output.include?('Unable to Install')
36
+ error_message = output.match(/Unable to Install.*\n.*NSLocalizedRecoverySuggestion = ([^\n]+)/)&.[](1)
37
+ check_device_compatibility(ipa_path)
38
+ raise "Failed to install app: #{error_message || 'Unknown error'}"
39
+ else
40
+ check_device_compatibility(ipa_path)
41
+ raise "Failed to install app: #{output}"
42
+ end
43
+ end
44
+
45
+ success = $CHILD_STATUS.success?
46
+ unless success
47
+ check_device_compatibility(ipa_path)
48
+ raise "Installation failed with exit code #{$CHILD_STATUS.exitstatus}"
49
+ end
50
+ end
51
+ rescue Timeout::Error
52
+ raise 'Installation timed out after 30 seconds. The device might be locked or ' \
53
+ 'installation might be stuck. Try unlocking the device and trying again.'
54
+ end
55
+
56
+ true
57
+ end
58
+
59
+ def launch_app(bundle_id)
60
+ command = "xcrun devicectl device process launch --device #{@device_id} #{bundle_id}"
61
+ Logger.debug "Running command: #{command}"
62
+
63
+ begin
64
+ Timeout.timeout(30) do
65
+ output = `#{command} 2>&1`
66
+ success = $CHILD_STATUS.success?
67
+
68
+ unless success
69
+ Logger.debug "Launch command output: #{output}"
70
+ if output.include?('The operation couldn\'t be completed. Application is restricted')
71
+ raise 'Failed to launch app: The app is restricted. Make sure the device is ' \
72
+ 'unlocked and the app is allowed to run.'
73
+ elsif output.include?('The operation couldn\'t be completed. Unable to launch')
74
+ raise 'Failed to launch app: Unable to launch. The app might be in a bad state - ' \
75
+ 'try uninstalling and reinstalling.'
76
+ else
77
+ raise "Failed to launch app #{bundle_id} on device: #{output}"
78
+ end
79
+ end
80
+ end
81
+ rescue Timeout::Error
82
+ raise 'Launch timed out after 30 seconds. The device might be locked. ' \
83
+ 'Try unlocking the device and trying again.'
84
+ end
85
+
86
+ true
87
+ end
88
+
89
+ private
90
+
91
+ def check_device_compatibility(ipa_path)
92
+ supported_platforms = XcodeDeviceManager.get_supported_platforms(ipa_path)
93
+ Logger.debug "Supported platforms: #{supported_platforms.join(', ')}"
94
+
95
+ unless supported_platforms.include?('iPhoneOS')
96
+ raise 'This build is not compatible with physical devices. Please use a simulator ' \
97
+ 'or make your build compatible with physical devices.'
98
+ end
99
+
100
+ Logger.debug 'Build is compatible with physical devices'
101
+ end
102
+
103
+ def extract_bundle_id_from_error(output)
104
+ # Extract bundle ID from error message like "...profile for com.emerge.hn.Hacker-News :"
105
+ output.match(/profile for ([\w\.-]+) :/)&.[](1) || 'unknown bundle ID'
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,114 @@
1
+ require 'English'
2
+ require 'zip'
3
+ require 'cfpropertylist'
4
+ require 'fileutils'
5
+
6
+ module EmergeCLI
7
+ class XcodeSimulator
8
+ attr_reader :device_id
9
+
10
+ def initialize(device_id, environment: Environment.new)
11
+ @device_id = device_id
12
+ @environment = environment
13
+ end
14
+
15
+ def boot
16
+ Logger.info "Booting simulator #{@device_id}..."
17
+ output = @environment.execute_command("xcrun simctl boot #{@device_id}")
18
+ raise 'Failed to boot simulator' if output.include?('error') || output.include?('failed')
19
+ end
20
+
21
+ def install_app(ipa_path)
22
+ raise "Non-IPA file provided: #{ipa_path}" unless ipa_path.end_with?('.ipa')
23
+
24
+ Dir.mktmpdir do |tmp_dir|
25
+ Logger.debug "Extracting .app from .ipa in temporary directory: #{tmp_dir}"
26
+
27
+ Zip::File.open(ipa_path) do |zip_file|
28
+ # Debug: List all entries to see what's in the IPA
29
+ Logger.debug 'IPA contents:'
30
+ zip_file.each do |entry|
31
+ Logger.debug " #{entry.name}"
32
+ end
33
+
34
+ # Try different patterns to find the .app directory
35
+ app_entry = zip_file.glob('**/*.app/').first ||
36
+ zip_file.glob('**/*.app').first ||
37
+ zip_file.find { |entry| entry.name.end_with?('.app/') || entry.name.end_with?('.app') }
38
+
39
+ raise 'No .app found in .ipa file' unless app_entry
40
+ Logger.debug "Found app entry: #{app_entry.name}"
41
+
42
+ # Extract the .app directory and its contents
43
+ app_dir = app_entry.name.end_with?('/') ? app_entry.name.chomp('/') : app_entry.name
44
+ pattern = "#{File.dirname(app_dir)}/#{File.basename(app_dir)}/**/*"
45
+ Logger.debug "Using glob pattern: #{pattern}"
46
+
47
+ zip_file.glob(pattern).each do |entry|
48
+ entry_path = File.join(tmp_dir, entry.name)
49
+ FileUtils.mkdir_p(File.dirname(entry_path))
50
+ zip_file.extract(entry, entry_path) unless File.exist?(entry_path)
51
+ end
52
+
53
+ extracted_app = Dir.glob(File.join(tmp_dir, '**/*.app')).first
54
+ raise 'Failed to extract .app from .ipa' unless extracted_app
55
+ Logger.debug "Extracted app at: #{extracted_app}"
56
+
57
+ install_extracted_app(extracted_app)
58
+ end
59
+ end
60
+ end
61
+
62
+ def launch_app(bundle_id)
63
+ command = "xcrun simctl launch #{@device_id} #{bundle_id}"
64
+ Logger.debug "Running command: #{command}"
65
+
66
+ output = `#{command} 2>&1`
67
+ success = $CHILD_STATUS.success?
68
+
69
+ unless success
70
+ Logger.debug "Launch command output: #{output}"
71
+ raise "Failed to launch app #{bundle_id} on simulator"
72
+ end
73
+
74
+ true
75
+ end
76
+
77
+ private
78
+
79
+ def install_extracted_app(app_path)
80
+ command = "xcrun simctl install #{@device_id} \"#{app_path}\""
81
+ Logger.debug "Running command: #{command}"
82
+
83
+ output = `#{command} 2>&1`
84
+ success = $CHILD_STATUS.success?
85
+
86
+ return if success
87
+ Logger.debug "Install command output: #{output}"
88
+ check_simulator_compatibility(app_path)
89
+ raise "Failed to install build on simulator #{@device_id}"
90
+ end
91
+
92
+ def check_simulator_compatibility(app_path)
93
+ supported_platforms = if app_path.end_with?('.ipa')
94
+ XcodeDeviceManager.get_supported_platforms(app_path)
95
+ else
96
+ info_plist_path = File.join(app_path, 'Info.plist')
97
+ raise 'Info.plist not found in app bundle' unless File.exist?(info_plist_path)
98
+
99
+ plist = CFPropertyList::List.new(file: info_plist_path)
100
+ info_plist = CFPropertyList.native_types(plist.value)
101
+ info_plist['CFBundleSupportedPlatforms'] || []
102
+ end
103
+
104
+ Logger.debug "Supported platforms: #{supported_platforms.join(', ')}"
105
+
106
+ unless supported_platforms.include?('iPhoneSimulator')
107
+ raise 'This build is not compatible with simulators. ' \
108
+ 'Please use a real device or make your build compatible with simulators.'
109
+ end
110
+
111
+ Logger.debug 'Build is compatible with simulators'
112
+ end
113
+ end
114
+ end
data/lib/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module EmergeCLI
2
- VERSION = '0.5.0'.freeze
2
+ VERSION = '0.6.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.5.0
4
+ version: 0.6.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-14 00:00:00.000000000 Z
11
+ date: 2025-01-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: async-http
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: 0.86.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: base64
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 0.2.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 0.2.0
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: CFPropertyList
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -72,6 +86,20 @@ dependencies:
72
86
  - - "~>"
73
87
  - !ruby/object:Gem::Version
74
88
  version: 1.2.0
89
+ - !ruby/object:Gem::Dependency
90
+ name: nkf
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: 0.1.3
96
+ type: :runtime
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: 0.1.3
75
103
  - !ruby/object:Gem::Dependency
76
104
  name: open3
77
105
  requirement: !ruby/object:Gem::Requirement
@@ -204,6 +232,7 @@ files:
204
232
  - lib/emerge_cli.rb
205
233
  - lib/reaper/ast_parser.rb
206
234
  - lib/reaper/code_deleter.rb
235
+ - lib/utils/environment.rb
207
236
  - lib/utils/git.rb
208
237
  - lib/utils/git_info_provider.rb
209
238
  - lib/utils/git_result.rb
@@ -214,6 +243,9 @@ files:
214
243
  - lib/utils/profiler.rb
215
244
  - lib/utils/project_detector.rb
216
245
  - lib/utils/version_check.rb
246
+ - lib/utils/xcode_device_manager.rb
247
+ - lib/utils/xcode_physical_device.rb
248
+ - lib/utils/xcode_simulator.rb
217
249
  - lib/version.rb
218
250
  - parsers/libtree-sitter-java-darwin-arm64.dylib
219
251
  - parsers/libtree-sitter-java-linux-x86_64.so