mysigner 0.1.1 → 0.1.3

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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.githooks/pre-commit +15 -0
  3. data/.githooks/pre-push +21 -0
  4. data/.github/workflows/ci.yml +29 -0
  5. data/.gitignore +1 -0
  6. data/.rubocop.yml +55 -0
  7. data/.rubocop_todo.yml +112 -0
  8. data/CHANGELOG.md +96 -0
  9. data/Gemfile +5 -3
  10. data/Gemfile.lock +38 -8
  11. data/README.md +87 -17
  12. data/Rakefile +5 -3
  13. data/bin/console +4 -3
  14. data/bin/setup +3 -0
  15. data/exe/mysigner +2 -1
  16. data/lib/mysigner/build/android_executor.rb +46 -52
  17. data/lib/mysigner/build/android_parser.rb +33 -40
  18. data/lib/mysigner/build/configurator.rb +17 -16
  19. data/lib/mysigner/build/detector.rb +39 -50
  20. data/lib/mysigner/build/error_analyzer.rb +70 -68
  21. data/lib/mysigner/build/executor.rb +30 -37
  22. data/lib/mysigner/build/parser.rb +18 -18
  23. data/lib/mysigner/cli/auth_commands.rb +735 -752
  24. data/lib/mysigner/cli/build_commands.rb +697 -721
  25. data/lib/mysigner/cli/concerns/actionable_suggestions.rb +208 -154
  26. data/lib/mysigner/cli/concerns/api_helpers.rb +46 -54
  27. data/lib/mysigner/cli/concerns/error_handlers.rb +247 -237
  28. data/lib/mysigner/cli/concerns/helpers.rb +12 -1
  29. data/lib/mysigner/cli/diagnostic_commands.rb +659 -635
  30. data/lib/mysigner/cli/resource_commands.rb +1266 -822
  31. data/lib/mysigner/cli/validate_commands.rb +161 -0
  32. data/lib/mysigner/cli.rb +5 -1
  33. data/lib/mysigner/client.rb +27 -19
  34. data/lib/mysigner/config.rb +93 -56
  35. data/lib/mysigner/export/exporter.rb +32 -36
  36. data/lib/mysigner/signing/certificate_checker.rb +18 -23
  37. data/lib/mysigner/signing/keystore_manager.rb +34 -39
  38. data/lib/mysigner/signing/validator.rb +38 -40
  39. data/lib/mysigner/signing/wizard.rb +329 -342
  40. data/lib/mysigner/upload/app_store_automation.rb +51 -49
  41. data/lib/mysigner/upload/app_store_submission.rb +87 -92
  42. data/lib/mysigner/upload/play_store_uploader.rb +98 -115
  43. data/lib/mysigner/upload/uploader.rb +101 -109
  44. data/lib/mysigner/version.rb +3 -1
  45. data/lib/mysigner.rb +13 -11
  46. data/mysigner.gemspec +36 -33
  47. data/test_manual.rb +37 -36
  48. metadata +38 -16
@@ -1,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'English'
1
4
  require 'fileutils'
2
5
  require 'tmpdir'
3
6
 
@@ -9,31 +12,27 @@ module Mysigner
9
12
  def initialize(archive_path, output_dir: nil)
10
13
  @archive_path = File.expand_path(archive_path)
11
14
  @output_dir = output_dir || File.dirname(@archive_path)
12
-
15
+
13
16
  validate_archive!
14
17
  end
15
18
 
16
19
  def export!(method: :appstore, team_id: nil, signing_style: 'automatic')
17
20
  say_exporting(method)
18
-
21
+
19
22
  # Generate export options plist
20
23
  options_plist = generate_export_options(method, team_id, signing_style)
21
-
24
+
22
25
  begin
23
26
  # Run xcodebuild -exportArchive
24
27
  success = execute_export(options_plist)
25
-
26
- unless success
27
- raise ExportError, "Export failed. Check output above for errors."
28
- end
29
-
28
+
29
+ raise ExportError, 'Export failed. Check output above for errors.' unless success
30
+
30
31
  # Find the generated .ipa file
31
32
  ipa_path = find_ipa_file
32
-
33
- unless ipa_path
34
- raise ExportError, "Export reported success but .ipa file not found in: #{@output_dir}"
35
- end
36
-
33
+
34
+ raise ExportError, "Export reported success but .ipa file not found in: #{@output_dir}" unless ipa_path
35
+
37
36
  ipa_path
38
37
  ensure
39
38
  # Clean up temp plist
@@ -44,42 +43,40 @@ module Mysigner
44
43
  private
45
44
 
46
45
  def validate_archive!
47
- unless File.exist?(@archive_path)
48
- raise ExportError, "Archive not found: #{@archive_path}"
49
- end
50
-
51
- unless File.directory?(@archive_path) && @archive_path.end_with?('.xcarchive')
52
- raise ExportError, "Invalid archive: #{@archive_path} (must be a .xcarchive directory)"
53
- end
46
+ raise ExportError, "Archive not found: #{@archive_path}" unless File.exist?(@archive_path)
47
+
48
+ return if File.directory?(@archive_path) && @archive_path.end_with?('.xcarchive')
49
+
50
+ raise ExportError, "Invalid archive: #{@archive_path} (must be a .xcarchive directory)"
54
51
  end
55
52
 
56
53
  def generate_export_options(method, team_id, signing_style)
57
54
  require 'plist'
58
-
55
+
59
56
  options = {
60
57
  'method' => export_method_string(method),
61
58
  'uploadBitcode' => false,
62
59
  'uploadSymbols' => false,
63
60
  'compileBitcode' => false
64
61
  }
65
-
62
+
66
63
  # Add team ID if provided
67
64
  options['teamID'] = team_id if team_id
68
-
65
+
69
66
  # Signing style
70
67
  if signing_style.to_s.downcase == 'manual'
71
68
  options['signingStyle'] = 'manual'
72
- # Note: For manual signing, we'd need to specify provisioningProfiles
69
+ # NOTE: For manual signing, we'd need to specify provisioningProfiles
73
70
  # But since the archive was already signed during build, we can often omit this
74
71
  else
75
72
  options['signingStyle'] = 'automatic'
76
73
  options['signingCertificate'] = 'Apple Distribution'
77
74
  end
78
-
75
+
79
76
  # Create temp plist file
80
77
  plist_path = File.join(Dir.tmpdir, "exportOptions-#{Time.now.to_i}.plist")
81
78
  File.write(plist_path, options.to_plist)
82
-
79
+
83
80
  plist_path
84
81
  end
85
82
 
@@ -100,7 +97,7 @@ module Mysigner
100
97
 
101
98
  def execute_export(options_plist)
102
99
  FileUtils.mkdir_p(@output_dir)
103
- puts ""
100
+ puts ''
104
101
 
105
102
  cmd = [
106
103
  'xcodebuild',
@@ -112,39 +109,38 @@ module Mysigner
112
109
  ].join(' ')
113
110
 
114
111
  # Run command and capture output
115
- IO.popen(cmd, err: [:child, :out]) do |io|
112
+ IO.popen(cmd, err: %i[child out]) do |io|
116
113
  io.each_line do |line|
117
114
  next if line.strip.empty?
118
-
115
+
119
116
  # Show errors and warnings
120
117
  if line.include?('error:') || line.include?('warning:')
121
118
  puts line
122
119
  # Show progress markers
123
- elsif line.include?('Exporting') || line.include?('Processing') ||
120
+ elsif line.include?('Exporting') || line.include?('Processing') ||
124
121
  line.include?('Validating')
125
122
  print '.'
126
123
  end
127
124
  end
128
125
  end
129
126
 
130
- puts "" # New line after dots
131
-
132
- $?.success?
127
+ puts '' # New line after dots
128
+
129
+ $CHILD_STATUS.success?
133
130
  end
134
131
 
135
132
  def find_ipa_file
136
133
  # Look for .ipa files in output directory
137
134
  ipa_files = Dir.glob(File.join(@output_dir, '*.ipa'))
138
-
135
+
139
136
  # Return the most recently created one
140
137
  ipa_files.max_by { |f| File.mtime(f) }
141
138
  end
142
139
 
143
140
  def say_exporting(method)
144
141
  puts "📦 Exporting archive for #{method}..."
145
- puts ""
142
+ puts ''
146
143
  end
147
144
  end
148
145
  end
149
146
  end
150
-
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'open3'
2
4
  require 'time'
3
5
 
@@ -42,23 +44,21 @@ module Mysigner
42
44
  cmd = 'security find-identity -v -p codesigning'
43
45
  stdout, stderr, status = Open3.capture3(cmd)
44
46
 
45
- unless status.success?
46
- raise CheckError, "Failed to query certificates: #{stderr}"
47
- end
47
+ raise CheckError, "Failed to query certificates: #{stderr}" unless status.success?
48
48
 
49
49
  certificates = []
50
-
50
+
51
51
  # Parse output: " 1) HASH \"Certificate Name\""
52
52
  stdout.each_line do |line|
53
53
  next unless line =~ /\d+\)\s+([A-F0-9]+)\s+"([^"]+)"/
54
-
55
- hash = $1
56
- name = $2
57
-
54
+
55
+ hash = ::Regexp.last_match(1)
56
+ name = ::Regexp.last_match(2)
57
+
58
58
  # Get certificate details
59
59
  details = get_certificate_details(name)
60
60
  next unless details
61
-
61
+
62
62
  certificates << {
63
63
  hash: hash,
64
64
  name: name,
@@ -76,12 +76,10 @@ module Mysigner
76
76
  def get_certificate_details(name)
77
77
  # Get certificate in PEM format by name
78
78
  cmd = "security find-certificate -c \"#{name}\" -p"
79
- stdout, stderr, status = Open3.capture3(cmd)
79
+ stdout, _, status = Open3.capture3(cmd)
80
80
 
81
81
  # If not found, return nil
82
- if !status.success? || stdout.empty?
83
- return nil
84
- end
82
+ return nil if !status.success? || stdout.empty?
85
83
 
86
84
  # Save to temp file and use openssl to read expiry
87
85
  require 'tempfile'
@@ -92,12 +90,12 @@ module Mysigner
92
90
 
93
91
  # Get expiry date using openssl
94
92
  cmd = "openssl x509 -in #{temp.path} -noout -enddate"
95
- out, err, stat = Open3.capture3(cmd)
93
+ out, _, stat = Open3.capture3(cmd)
96
94
 
97
95
  if stat.success? && out =~ /notAfter=(.+)/
98
- expiry_str = $1.strip
96
+ expiry_str = ::Regexp.last_match(1).strip
99
97
  expires_at = Time.parse(expiry_str)
100
- days_until_expiry = ((expires_at - Time.now) / 86400).to_i
98
+ days_until_expiry = ((expires_at - Time.now) / 86_400).to_i
101
99
 
102
100
  return {
103
101
  expires_at: expires_at,
@@ -113,11 +111,9 @@ module Mysigner
113
111
 
114
112
  def extract_team_id(name)
115
113
  # Team ID is usually in parentheses at the end
116
- if name =~ /\(([A-Z0-9]{10})\)$/
117
- $1
118
- else
119
- nil
120
- end
114
+ return unless name =~ /\(([A-Z0-9]{10})\)$/
115
+
116
+ ::Regexp.last_match(1)
121
117
  end
122
118
 
123
119
  def determine_cert_type(name)
@@ -134,7 +130,7 @@ module Mysigner
134
130
  end
135
131
 
136
132
  def determine_status(days_until_expiry)
137
- if days_until_expiry < 0
133
+ if days_until_expiry.negative?
138
134
  STATUS_EXPIRED
139
135
  elsif days_until_expiry < 30
140
136
  STATUS_EXPIRING_SOON
@@ -145,4 +141,3 @@ module Mysigner
145
141
  end
146
142
  end
147
143
  end
148
-
@@ -1,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'English'
1
4
  require 'fileutils'
2
5
  require 'base64'
3
6
 
@@ -44,21 +47,19 @@ module Mysigner
44
47
  # Get keystore details
45
48
  keystores = list
46
49
  keystore = keystores.find { |k| k['id'].to_s == keystore_id.to_s }
47
-
48
- unless keystore
49
- raise KeystoreNotFoundError, "Keystore with ID #{keystore_id} not found"
50
- end
50
+
51
+ raise KeystoreNotFoundError, "Keystore with ID #{keystore_id} not found" unless keystore
51
52
 
52
53
  # Download the keystore file
53
54
  download_url = "/api/v1/organizations/#{@organization_id}/android_keystores/#{keystore_id}/download"
54
-
55
+
55
56
  conn = build_download_connection
56
57
  response = conn.get(download_url)
57
-
58
+
58
59
  unless response.success?
59
60
  error_msg = begin
60
61
  JSON.parse(response.body)['message']
61
- rescue
62
+ rescue StandardError
62
63
  "HTTP #{response.status}"
63
64
  end
64
65
  raise DownloadError, "Failed to download keystore: #{error_msg}"
@@ -67,9 +68,9 @@ module Mysigner
67
68
  # Save to local file
68
69
  filename = "#{keystore['name'].gsub(/[^a-zA-Z0-9_.-]/, '_')}.jks"
69
70
  local_path = File.join(KEYSTORES_DIR, filename)
70
-
71
+
71
72
  File.binwrite(local_path, response.body)
72
- File.chmod(0600, local_path) # Secure permissions
73
+ File.chmod(0o600, local_path) # Secure permissions
73
74
 
74
75
  {
75
76
  path: local_path,
@@ -83,10 +84,8 @@ module Mysigner
83
84
  def get_or_download(keystore_id)
84
85
  keystores = list
85
86
  keystore = keystores.find { |k| k['id'].to_s == keystore_id.to_s }
86
-
87
- unless keystore
88
- raise KeystoreNotFoundError, "Keystore with ID #{keystore_id} not found"
89
- end
87
+
88
+ raise KeystoreNotFoundError, "Keystore with ID #{keystore_id} not found" unless keystore
90
89
 
91
90
  # Check if already cached locally
92
91
  filename = "#{keystore['name'].gsub(/[^a-zA-Z0-9_.-]/, '_')}.jks"
@@ -109,10 +108,9 @@ module Mysigner
109
108
  end
110
109
 
111
110
  # Upload a keystore to API
112
- def upload(name:, keystore_path:, keystore_password:, key_alias:, key_password: nil, android_app_id: nil, active: true)
113
- unless File.exist?(keystore_path)
114
- raise KeystoreError, "Keystore file not found: #{keystore_path}"
115
- end
111
+ def upload(name:, keystore_path:, keystore_password:, key_alias:, key_password: nil, android_app_id: nil,
112
+ active: true)
113
+ raise KeystoreError, "Keystore file not found: #{keystore_path}" unless File.exist?(keystore_path)
116
114
 
117
115
  # Read and encode keystore
118
116
  keystore_content = File.binread(keystore_path)
@@ -141,14 +139,18 @@ module Mysigner
141
139
  # Delete a keystore
142
140
  def delete(keystore_id)
143
141
  @client.delete("/api/v1/organizations/#{@organization_id}/android_keystores/#{keystore_id}")
144
-
142
+
145
143
  # Also remove local cached file
146
- keystores = list rescue []
144
+ keystores = begin
145
+ list
146
+ rescue StandardError
147
+ []
148
+ end
147
149
  keystore = keystores.find { |k| k['id'].to_s == keystore_id.to_s }
148
150
  if keystore
149
151
  filename = "#{keystore['name'].gsub(/[^a-zA-Z0-9_.-]/, '_')}.jks"
150
152
  local_path = File.join(KEYSTORES_DIR, filename)
151
- File.delete(local_path) if File.exist?(local_path)
153
+ FileUtils.rm_f(local_path)
152
154
  end
153
155
 
154
156
  true
@@ -187,26 +189,18 @@ module Mysigner
187
189
  return nil unless system('which keytool > /dev/null 2>&1')
188
190
 
189
191
  output = `keytool -list -v -keystore #{shell_escape(keystore_path)} -storepass #{shell_escape(password)} 2>&1`
190
- return nil unless $?.success?
192
+ return nil unless $CHILD_STATUS.success?
191
193
 
192
194
  # Parse output
193
195
  info = {}
194
-
195
- if output =~ /Alias name: (.+)/
196
- info[:aliases] = output.scan(/Alias name: (.+)/).flatten
197
- end
198
-
199
- if output =~ /Valid from: .+ until: (.+)/
200
- info[:expires] = $1
201
- end
202
-
203
- if output =~ /SHA256: (.+)/
204
- info[:sha256] = $1.strip
205
- end
206
-
207
- if output =~ /SHA1: (.+)/
208
- info[:sha1] = $1.strip
209
- end
196
+
197
+ info[:aliases] = output.scan(/Alias name: (.+)/).flatten if output =~ /Alias name: (.+)/
198
+
199
+ info[:expires] = ::Regexp.last_match(1) if output =~ /Valid from: .+ until: (.+)/
200
+
201
+ info[:sha256] = ::Regexp.last_match(1).strip if output =~ /SHA256: (.+)/
202
+
203
+ info[:sha1] = ::Regexp.last_match(1).strip if output =~ /SHA1: (.+)/
210
204
 
211
205
  info
212
206
  end
@@ -215,7 +209,7 @@ module Mysigner
215
209
 
216
210
  def ensure_keystores_dir
217
211
  FileUtils.mkdir_p(KEYSTORES_DIR)
218
- File.chmod(0700, KEYSTORES_DIR) # Secure permissions
212
+ File.chmod(0o700, KEYSTORES_DIR) # Secure permissions
219
213
  end
220
214
 
221
215
  def build_download_connection
@@ -232,7 +226,8 @@ module Mysigner
232
226
 
233
227
  def shell_escape(str)
234
228
  return "''" if str.nil? || str.empty?
235
- "'" + str.gsub("'", "'\\''") + "'"
229
+
230
+ "'#{str.gsub("'", "'\\''")}'"
236
231
  end
237
232
  end
238
233
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Mysigner
2
4
  module Signing
3
5
  class Validator
@@ -19,20 +21,20 @@ module Mysigner
19
21
  team_id = @team_id_override || @parser.team_id(@target_name, @configuration)
20
22
  if team_id.nil? || team_id.empty?
21
23
  result[:errors] << "No development team set for target '#{@target_name}'"
22
- result[:errors] << ""
23
- result[:errors] << "Fix Option 1: Add team to My Signer"
24
- result[:errors] << " 1. Open https://mysigner.dev"
25
- result[:errors] << " 2. Go to Settings → App Store Connect"
26
- result[:errors] << " 3. Add your Team ID"
27
- result[:errors] << " 4. Run: mysigner build (team will auto-fetch)"
28
- result[:errors] << ""
29
- result[:errors] << "Fix Option 2: Pass team via CLI"
30
- result[:errors] << " mysigner build --team YOUR_TEAM_ID"
31
- result[:errors] << ""
32
- result[:errors] << "Fix Option 3: Set in Xcode"
33
- result[:errors] << " Open Xcode → Select target → Signing & Capabilities → Select a team"
34
- result[:errors] << ""
35
- result[:errors] << "Find your team ID at: https://developer.apple.com/account/#!/membership/"
24
+ result[:errors] << ''
25
+ result[:errors] << 'Fix Option 1: Add team to My Signer'
26
+ result[:errors] << ' 1. Open https://mysigner.dev'
27
+ result[:errors] << ' 2. Go to Settings → App Store Connect'
28
+ result[:errors] << ' 3. Add your Team ID'
29
+ result[:errors] << ' 4. Run: mysigner build (team will auto-fetch)'
30
+ result[:errors] << ''
31
+ result[:errors] << 'Fix Option 2: Pass team via CLI'
32
+ result[:errors] << ' mysigner build --team YOUR_TEAM_ID'
33
+ result[:errors] << ''
34
+ result[:errors] << 'Fix Option 3: Set in Xcode'
35
+ result[:errors] << ' Open Xcode → Select target → Signing & Capabilities → Select a team'
36
+ result[:errors] << ''
37
+ result[:errors] << 'Find your team ID at: https://developer.apple.com/account/#!/membership/'
36
38
  result[:valid] = false
37
39
  elsif @team_id_override && @parser.team_id(@target_name, @configuration).nil?
38
40
  result[:warnings] << "Using team from My Signer: #{@team_id_override}"
@@ -42,15 +44,13 @@ module Mysigner
42
44
 
43
45
  # Check 2: Code signing style
44
46
  signing_style = @parser.code_sign_style(@target_name, @configuration)
45
- if signing_style.nil?
46
- result[:warnings] << "Code signing style not explicitly set (will use Xcode default)"
47
- end
47
+ result[:warnings] << 'Code signing style not explicitly set (will use Xcode default)' if signing_style.nil?
48
48
 
49
49
  # Check 3: Bundle ID
50
50
  bundle_id = @parser.bundle_id(@target_name, @configuration)
51
51
  if bundle_id.nil? || bundle_id.empty? || bundle_id.include?('$(')
52
52
  result[:errors] << "Bundle ID not set or contains variables: #{bundle_id}"
53
- result[:errors] << "Fix: Open Xcode → Select target → General → Bundle Identifier"
53
+ result[:errors] << 'Fix: Open Xcode → Select target → General → Bundle Identifier'
54
54
  result[:valid] = false
55
55
  end
56
56
 
@@ -79,22 +79,22 @@ module Mysigner
79
79
  result = validate
80
80
 
81
81
  if result[:warnings].any?
82
- puts ""
83
- puts "⚠️ Warnings:"
82
+ puts ''
83
+ puts '⚠️ Warnings:'
84
84
  result[:warnings].each { |w| puts " • #{w}" }
85
85
  end
86
86
 
87
87
  if result[:errors].any?
88
- puts ""
89
- puts "❌ Validation Failed:"
90
- puts ""
88
+ puts ''
89
+ puts '❌ Validation Failed:'
90
+ puts ''
91
91
  result[:errors].each { |e| puts " • #{e}" }
92
- puts ""
93
- raise ValidationError, "Pre-build validation failed. Fix the errors above and try again."
92
+ puts ''
93
+ raise ValidationError, 'Pre-build validation failed. Fix the errors above and try again.'
94
94
  end
95
95
 
96
- puts "✓ Pre-build validation passed" if result[:warnings].empty?
97
- puts "" if result[:warnings].any?
96
+ puts '✓ Pre-build validation passed' if result[:warnings].empty?
97
+ puts '' if result[:warnings].any?
98
98
  end
99
99
 
100
100
  private
@@ -104,19 +104,17 @@ module Mysigner
104
104
 
105
105
  # Check for valid iOS distribution certificates in keychain
106
106
  output = `security find-identity -v -p codesigning 2>&1`
107
-
107
+
108
108
  if output.include?('0 valid identities found')
109
- result[:errors] << "No code signing certificates found in keychain"
110
- result[:errors] << "Fix: Install your distribution certificate (.p12 file)"
109
+ result[:errors] << 'No code signing certificates found in keychain'
110
+ result[:errors] << 'Fix: Install your distribution certificate (.p12 file)'
111
111
  else
112
112
  # Count valid certificates
113
113
  cert_count = output.scan(/\d+\)\s+[A-F0-9]+/).count
114
114
  result[:warnings] << "Found #{cert_count} code signing certificate(s) in keychain"
115
-
115
+
116
116
  # Check for expired certificates
117
- if output.include?('CSSMERR')
118
- result[:warnings] << "Some certificates may be invalid or expired"
119
- end
117
+ result[:warnings] << 'Some certificates may be invalid or expired' if output.include?('CSSMERR')
120
118
  end
121
119
 
122
120
  result
@@ -127,18 +125,18 @@ module Mysigner
127
125
 
128
126
  # Check provisioning profile directory
129
127
  profiles_dir = File.expand_path('~/Library/MobileDevice/Provisioning Profiles')
130
-
128
+
131
129
  unless Dir.exist?(profiles_dir)
132
- result[:warnings] << "No provisioning profiles directory found"
133
- result[:warnings] << "Profiles will be downloaded automatically if using automatic signing"
130
+ result[:warnings] << 'No provisioning profiles directory found'
131
+ result[:warnings] << 'Profiles will be downloaded automatically if using automatic signing'
134
132
  return result
135
133
  end
136
134
 
137
135
  profiles = Dir.glob("#{profiles_dir}/*.mobileprovision")
138
-
136
+
139
137
  if profiles.empty?
140
- result[:warnings] << "No provisioning profiles found"
141
- result[:warnings] << "Profiles will be downloaded automatically if using automatic signing"
138
+ result[:warnings] << 'No provisioning profiles found'
139
+ result[:warnings] << 'Profiles will be downloaded automatically if using automatic signing'
142
140
  else
143
141
  result[:warnings] << "Found #{profiles.count} provisioning profile(s) installed"
144
142
  end