mysigner 0.3.4 → 0.3.7

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +2 -2
  3. data/CHANGELOG.md +47 -0
  4. data/Gemfile +0 -1
  5. data/Gemfile.lock +2 -6
  6. data/README.md +16 -16
  7. data/lib/mysigner/build/android_executor.rb +16 -21
  8. data/lib/mysigner/build/detector.rb +3 -1
  9. data/lib/mysigner/build/executor.rb +6 -1
  10. data/lib/mysigner/cli/auth_commands.rb +14 -3
  11. data/lib/mysigner/cli/build_commands.rb +14 -11
  12. data/lib/mysigner/cli/concerns/actionable_suggestions.rb +1 -2
  13. data/lib/mysigner/cli/concerns/api_helpers.rb +16 -3
  14. data/lib/mysigner/cli/concerns/error_handlers.rb +0 -1
  15. data/lib/mysigner/cli/concerns/helpers.rb +14 -14
  16. data/lib/mysigner/cli/diagnostic_commands.rb +16 -5
  17. data/lib/mysigner/cli/resource_commands.rb +8 -1
  18. data/lib/mysigner/client.rb +52 -0
  19. data/lib/mysigner/config.rb +9 -4
  20. data/lib/mysigner/export/exporter.rb +6 -1
  21. data/lib/mysigner/formatting.rb +23 -0
  22. data/lib/mysigner/signing/certificate_checker.rb +6 -6
  23. data/lib/mysigner/signing/keystore_manager.rb +2 -0
  24. data/lib/mysigner/signing/wizard.rb +2 -0
  25. data/lib/mysigner/upload/app_store_automation.rb +13 -1
  26. data/lib/mysigner/upload/app_store_submission.rb +2 -14
  27. data/lib/mysigner/upload/asc_rest_uploader.rb +44 -3
  28. data/lib/mysigner/upload/asc_submitter.rb +5 -0
  29. data/lib/mysigner/upload/play_store_uploader.rb +2 -7
  30. data/lib/mysigner/upload/uploader.rb +9 -366
  31. data/lib/mysigner/version.rb +1 -1
  32. data/lib/mysigner.rb +1 -0
  33. data/mysigner.gemspec +6 -2
  34. metadata +2 -20
  35. data/.travis.yml +0 -7
  36. data/MANUAL_TEST.md +0 -341
  37. data/bin/console +0 -15
  38. data/bin/setup +0 -11
  39. data/test_manual.rb +0 -103
@@ -1,28 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'English'
4
- require 'fileutils'
5
- require 'json'
3
+ require 'open3'
6
4
  require 'tmpdir'
7
5
 
8
6
  module Mysigner
9
7
  module Upload
8
+ # IPA metadata reader. The legacy iTMSTransporter/altool *instance*
9
+ # uploader that used to live here was removed once the iOS upload path
10
+ # moved to AscRestUploader/AscSubmitter — only this class method is still
11
+ # used (by build_commands.rb to read an IPA's build info before upload).
10
12
  class Uploader
11
- class UploadError < Mysigner::Error; end
12
- class TransporterNotFoundError < UploadError; end
13
- class AuthenticationError < UploadError; end
14
-
15
- TRANSPORTER_PATHS = [
16
- '/Applications/Xcode.app/Contents/Developer/usr/bin/iTMSTransporter', # Bundled with Xcode (primary)
17
- '/Applications/Transporter.app/Contents/itms/bin/iTMSTransporter', # Standalone Transporter app
18
- '/usr/local/itms/bin/iTMSTransporter' # Custom installation
19
- ].freeze
20
-
21
- # Parses CFBundleVersion + CFBundleShortVersionString from an .ipa
22
- # (reads Info.plist from the Payload/*.app/ inside the zip).
23
- # Used by the new ASC REST upload flow in build_commands.rb.
13
+ # Parses CFBundleVersion + CFBundleShortVersionString + CFBundleIdentifier
14
+ # from an .ipa (reads Info.plist from the Payload/*.app/ inside the zip).
24
15
  def self.extract_ipa_info(ipa_path)
25
- require 'open3'
26
16
  result = { cf_bundle_version: nil, cf_bundle_short_version_string: nil, bundle_id: nil }
27
17
  Dir.mktmpdir('mysigner-ipa-inspect-') do |tmp|
28
18
  plist_path = File.join(tmp, 'Info.plist')
@@ -34,361 +24,14 @@ module Mysigner
34
24
  return result unless xml_status.success?
35
25
 
36
26
  result[:cf_bundle_version] = xml_out[%r{<key>CFBundleVersion</key>\s*<string>([^<]+)</string>}, 1]
37
- result[:cf_bundle_short_version_string] = xml_out[%r{<key>CFBundleShortVersionString</key>\s*<string>([^<]+)</string>}, 1]
27
+ result[:cf_bundle_short_version_string] =
28
+ xml_out[%r{<key>CFBundleShortVersionString</key>\s*<string>([^<]+)</string>}, 1]
38
29
  result[:bundle_id] = xml_out[%r{<key>CFBundleIdentifier</key>\s*<string>([^<]+)</string>}, 1]
39
30
  end
40
31
  result
41
32
  rescue StandardError
42
33
  { cf_bundle_version: nil, cf_bundle_short_version_string: nil, bundle_id: nil }
43
34
  end
44
-
45
- def initialize(ipa_path, api_key:, api_issuer:, private_key:)
46
- @ipa_path = File.expand_path(ipa_path)
47
- @api_key = api_key
48
- @api_issuer = api_issuer
49
- @private_key = private_key
50
-
51
- validate_ipa!
52
- detect_transporter!
53
- setup_private_key!
54
- end
55
-
56
- def upload!(wait_for_processing: false)
57
- say_uploading
58
-
59
- begin
60
- # Validate IPA first
61
- validate_result = validate_ipa
62
- raise UploadError, "IPA validation failed: #{validate_result[:error]}" unless validate_result[:success]
63
-
64
- # Upload to App Store Connect
65
- upload_result = upload_ipa
66
- raise UploadError, "Upload failed: #{upload_result[:error]}" unless upload_result[:success]
67
-
68
- say_success
69
-
70
- # Optionally wait for processing
71
- if wait_for_processing
72
- say_waiting_for_processing
73
- # NOTE: Build processing status is polled via the dashboard's sync API
74
- # in build_commands.rb, not directly here. This flag is reserved for
75
- # future use or manual uploads outside the `ship` command flow.
76
- end
77
-
78
- {
79
- success: true,
80
- message: 'Upload completed successfully'
81
- }
82
- rescue StandardError => e
83
- raise UploadError, "Upload failed: #{e.message}"
84
- ensure
85
- cleanup_private_key!
86
- end
87
- end
88
-
89
- private
90
-
91
- def validate_ipa!
92
- raise UploadError, "IPA file not found: #{@ipa_path}" unless File.exist?(@ipa_path)
93
-
94
- raise UploadError, "Invalid file type: #{@ipa_path} (must be .ipa)" unless @ipa_path.end_with?('.ipa')
95
-
96
- # Check file size (should be at least 10KB to catch truly corrupt files)
97
- file_size = File.size(@ipa_path)
98
- return unless file_size < 10_000
99
-
100
- raise UploadError, "IPA file seems too small: #{file_size} bytes (possible corruption)"
101
- end
102
-
103
- def detect_transporter!
104
- # We primarily use xcrun altool, but check for iTMSTransporter as fallback
105
- @transporter_path = TRANSPORTER_PATHS.find { |path| File.exist?(path) }
106
-
107
- # Even if iTMSTransporter is not found, xcrun altool should work
108
- # This is just for informational purposes
109
- @using_altool = true
110
- end
111
-
112
- def setup_private_key!
113
- # Phase 0: write the .p8 to a per-run tempdir (0600) and point altool
114
- # there via API_PRIVATE_KEYS_DIR. #cleanup_private_key! in the upload!
115
- # ensure block removes the tempdir, so the key never persists across
116
- # invocations. Replaces the old ~/.private_keys/ persistent write.
117
- @private_keys_dir = Dir.mktmpdir('mysigner-p8-')
118
- @private_key_path = File.join(@private_keys_dir, "AuthKey_#{@api_key}.p8")
119
- File.write(@private_key_path, @private_key)
120
- File.chmod(0o600, @private_key_path)
121
- ENV['API_PRIVATE_KEYS_DIR'] = @private_keys_dir
122
- end
123
-
124
- def cleanup_private_key!
125
- FileUtils.rm_rf(@private_keys_dir) if @private_keys_dir && Dir.exist?(@private_keys_dir)
126
- ENV.delete('API_PRIVATE_KEYS_DIR')
127
- rescue StandardError
128
- # Best-effort cleanup; never raise from ensure.
129
- end
130
-
131
- def validate_ipa
132
- puts '📋 Validating IPA...'
133
- puts ''
134
-
135
- # Try altool first (simpler, works today)
136
- if altool_available?
137
- validate_with_altool
138
- elsif @transporter_path
139
- validate_with_transporter
140
- else
141
- puts '⚠️ Skipping validation (no tools available)'
142
- { success: true } # Continue anyway
143
- end
144
- end
145
-
146
- def validate_with_altool
147
- cmd = [
148
- 'xcrun', 'altool',
149
- '--validate-app',
150
- '-f', @ipa_path,
151
- '-t', 'ios',
152
- '--apiKey', @api_key,
153
- '--apiIssuer', @api_issuer
154
- ].join(' ')
155
-
156
- output = `#{cmd} 2>&1`
157
- success = $CHILD_STATUS.success?
158
-
159
- if success
160
- puts '✓ Validation passed (altool)'
161
- { success: true }
162
- else
163
- error_message = extract_error_from_output(output)
164
- puts "✗ Validation failed: #{error_message}"
165
- { success: false, error: error_message }
166
- end
167
- end
168
-
169
- def validate_with_transporter
170
- # iTMSTransporter verify mode
171
- # According to https://help.apple.com/itc/transporteruserguide/en.lproj/static.html
172
- cmd = [
173
- @transporter_path,
174
- '-m', 'verify',
175
- '-f', @ipa_path,
176
- '-apiKey', @api_key,
177
- '-apiIssuer', @api_issuer,
178
- '-t', 'Signiant' # Transport mode
179
- ].join(' ')
180
-
181
- output = `#{cmd} 2>&1`
182
- success = $CHILD_STATUS.success?
183
-
184
- if success
185
- puts '✓ Validation passed (iTMSTransporter)'
186
- { success: true }
187
- else
188
- error_message = extract_error_from_output(output)
189
- puts "✗ Validation failed: #{error_message}"
190
- { success: false, error: error_message }
191
- end
192
- end
193
-
194
- def upload_ipa
195
- puts ''
196
- puts '☁️ Uploading to App Store Connect...'
197
- puts ''
198
-
199
- # Try altool first, fall back to iTMSTransporter
200
- if altool_available?
201
- upload_with_altool
202
- elsif @transporter_path
203
- upload_with_transporter
204
- else
205
- raise UploadError, 'No upload tool available. Please ensure Xcode is installed.'
206
- end
207
- end
208
-
209
- def upload_with_altool
210
- puts 'Using: xcrun altool'
211
- puts ''
212
-
213
- cmd = [
214
- 'xcrun', 'altool',
215
- '--upload-app',
216
- '-f', @ipa_path,
217
- '-t', 'ios',
218
- '--apiKey', @api_key,
219
- '--apiIssuer', @api_issuer
220
- ].join(' ')
221
-
222
- # Capture full output for error detection
223
- output = []
224
- has_errors = false
225
-
226
- # Run with live output
227
- IO.popen(cmd, err: %i[child out]) do |io|
228
- io.each_line do |line|
229
- output << line
230
- next if line.strip.empty?
231
-
232
- # Detect errors
233
- has_errors = true if line.include?('ERROR') || line.include?('UPLOAD FAILED')
234
-
235
- # Show progress indicators
236
- if line.include?('Uploading') || line.include?('Processing') ||
237
- line.include?('Verifying') || line.include?('Package')
238
- print '.'
239
- elsif line.include?('error') || line.include?('ERROR')
240
- puts ''
241
- puts line
242
- elsif line.include?('SUCCESS') || line.include?('No errors')
243
- puts ''
244
- puts line
245
- end
246
- end
247
- end
248
-
249
- puts '' # New line after progress dots
250
-
251
- # Check both exit code and output for errors
252
- if $CHILD_STATUS.success? && !has_errors
253
- { success: true }
254
- else
255
- error_msg = extract_error_from_output(output.join("\n"))
256
- { success: false, error: error_msg }
257
- end
258
- end
259
-
260
- def upload_with_transporter
261
- puts 'Using: iTMSTransporter (future-proof)'
262
- puts ''
263
-
264
- # iTMSTransporter upload mode
265
- # According to https://help.apple.com/itc/transporteruserguide/en.lproj/static.html
266
- cmd = [
267
- @transporter_path,
268
- '-m', 'upload',
269
- '-f', @ipa_path,
270
- '-apiKey', @api_key,
271
- '-apiIssuer', @api_issuer,
272
- '-t', 'Signiant' # Transport mode (can also use 'Aspera' or 'DAV')
273
- ].join(' ')
274
-
275
- # Capture output
276
- output = []
277
-
278
- IO.popen(cmd, err: %i[child out]) do |io|
279
- io.each_line do |line|
280
- output << line
281
- next if line.strip.empty?
282
-
283
- # Show progress
284
- if line.include?('Uploading') || line.include?('Processing') ||
285
- line.include?('Verifying')
286
- print '.'
287
- elsif line.include?('ERROR')
288
- puts ''
289
- puts line
290
- elsif line.include?('SUCCESS')
291
- puts ''
292
- puts line
293
- end
294
- end
295
- end
296
-
297
- puts ''
298
-
299
- if $CHILD_STATUS.success?
300
- { success: true }
301
- else
302
- error_msg = extract_error_from_output(output.join("\n"))
303
- { success: false, error: error_msg }
304
- end
305
- end
306
-
307
- def altool_available?
308
- # Check if altool is available
309
- system('xcrun --find altool > /dev/null 2>&1')
310
- end
311
-
312
- def extract_error_from_output(output)
313
- # Try to extract meaningful error from altool output
314
- error_lines = output.lines.select do |l|
315
- l.include?('ERROR') || l.include?('error') || l.include?('Invalid')
316
- end
317
-
318
- if error_lines.any?
319
- # Clean up and join error messages
320
- cleaned_errors = error_lines.map(&:strip)
321
- .reject { |l| l.empty? || l.start_with?('code :') || l.start_with?('iris-code') }
322
- .join("\n")
323
-
324
- # Add helpful context for common errors
325
- if output.include?('Cannot determine the Apple ID from Bundle ID')
326
- cleaned_errors += "\n\n💡 This usually means:\n"
327
- cleaned_errors += " • Your bundle ID doesn't have an App created in App Store Connect\n"
328
- cleaned_errors += " • Or your Xcode project's bundle ID doesn't match the App's bundle ID\n"
329
- cleaned_errors += "\n Fix:\n"
330
- cleaned_errors += " 1. Check your app in App Store Connect → App Information\n"
331
- cleaned_errors += " 2. Note the Bundle ID shown there\n"
332
- cleaned_errors += " 3. Either:\n"
333
- cleaned_errors += " a) Update your Xcode project to use that bundle ID\n"
334
- cleaned_errors += " b) Or run: mysigner ship testflight --bundle-id <correct-bundle-id>\n"
335
- elsif output.include?('Missing required icon file')
336
- cleaned_errors += "\n\n💡 Your app is missing required icon assets.\n"
337
- cleaned_errors += " Fix:\n"
338
- cleaned_errors += " 1. Open your Xcode project\n"
339
- cleaned_errors += " 2. Go to Assets.xcassets → AppIcon\n"
340
- cleaned_errors += " 3. Add icon images for all required sizes\n"
341
- cleaned_errors += " 4. Or use a tool like https://appicon.co to generate all sizes\n"
342
- end
343
-
344
- cleaned_errors
345
- else
346
- 'Unknown error'
347
- end
348
- end
349
-
350
- def say_uploading
351
- puts '☁️ Uploading to TestFlight...'
352
- puts ''
353
- puts "IPA: #{File.basename(@ipa_path)}"
354
- puts "Size: #{format_bytes(File.size(@ipa_path))}"
355
-
356
- # Show which tool will be used
357
- if altool_available?
358
- puts 'Tool: xcrun altool'
359
- elsif @transporter_path
360
- puts 'Tool: iTMSTransporter (future-proof)'
361
- else
362
- puts 'Tool: Auto-detect'
363
- end
364
- puts ''
365
- end
366
-
367
- def say_success
368
- puts ''
369
- puts '=' * 80
370
- puts '✓ Upload succeeded!'
371
- puts '=' * 80
372
- puts ''
373
- puts '🎉 Your build is now processing in App Store Connect'
374
- puts ''
375
- end
376
-
377
- def say_waiting_for_processing
378
- puts '⏳ Waiting for App Store Connect to process the build...'
379
- puts 'This may take 5-15 minutes...'
380
- puts ''
381
- end
382
-
383
- def format_bytes(bytes)
384
- if bytes < 1024
385
- "#{bytes} B"
386
- elsif bytes < 1024 * 1024
387
- "#{(bytes / 1024.0).round(1)} KB"
388
- else
389
- "#{(bytes / (1024.0 * 1024)).round(1)} MB"
390
- end
391
- end
392
35
  end
393
36
  end
394
37
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mysigner
4
- VERSION = '0.3.4'
4
+ VERSION = '0.3.7'
5
5
  end
data/lib/mysigner.rb CHANGED
@@ -5,6 +5,7 @@ module Mysigner
5
5
  end
6
6
 
7
7
  require 'mysigner/version'
8
+ require 'mysigner/formatting'
8
9
  require 'mysigner/config'
9
10
  require 'mysigner/local_credentials'
10
11
  require 'mysigner/client'
data/mysigner.gemspec CHANGED
@@ -58,7 +58,12 @@ Gem::Specification.new do |spec|
58
58
  # Specify which files should be added to the gem when it is released.
59
59
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
60
60
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
61
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
61
+ # Ship only what the installed gem needs. Exclude tests, the dev-only
62
+ # bin/ helpers (console/setup — the real CLI is exe/mysigner), and the
63
+ # internal MANUAL_TEST.md process notes.
64
+ `git ls-files -z`.split("\x0").reject do |f|
65
+ f.match(%r{\A(?:test|spec|features|bin)/}) || f == 'MANUAL_TEST.md'
66
+ end
62
67
  end
63
68
  spec.bindir = 'exe'
64
69
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
@@ -69,7 +74,6 @@ Gem::Specification.new do |spec|
69
74
  spec.add_dependency 'faraday', '~> 2.14'
70
75
  spec.add_dependency 'faraday-retry', '~> 2.2'
71
76
  spec.add_dependency 'plist', '~> 3.7'
72
- spec.add_dependency 'reline', '~> 0.5'
73
77
  spec.add_dependency 'thor', '~> 1.4'
74
78
  spec.add_dependency 'xcodeproj', '~> 1.27'
75
79
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mysigner
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.4
4
+ version: 0.3.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jurgen Leka
@@ -65,20 +65,6 @@ dependencies:
65
65
  - - "~>"
66
66
  - !ruby/object:Gem::Version
67
67
  version: '3.7'
68
- - !ruby/object:Gem::Dependency
69
- name: reline
70
- requirement: !ruby/object:Gem::Requirement
71
- requirements:
72
- - - "~>"
73
- - !ruby/object:Gem::Version
74
- version: '0.5'
75
- type: :runtime
76
- prerelease: false
77
- version_requirements: !ruby/object:Gem::Requirement
78
- requirements:
79
- - - "~>"
80
- - !ruby/object:Gem::Version
81
- version: '0.5'
82
68
  - !ruby/object:Gem::Dependency
83
69
  name: thor
84
70
  requirement: !ruby/object:Gem::Requirement
@@ -224,17 +210,13 @@ files:
224
210
  - ".rubocop.yml"
225
211
  - ".rubocop_todo.yml"
226
212
  - ".ruby-version"
227
- - ".travis.yml"
228
213
  - CHANGELOG.md
229
214
  - CODE_OF_CONDUCT.md
230
215
  - Gemfile
231
216
  - Gemfile.lock
232
217
  - LICENSE
233
- - MANUAL_TEST.md
234
218
  - README.md
235
219
  - Rakefile
236
- - bin/console
237
- - bin/setup
238
220
  - exe/mysigner
239
221
  - lib/mysigner.rb
240
222
  - lib/mysigner/auth/asc_jwt_minter.rb
@@ -261,6 +243,7 @@ files:
261
243
  - lib/mysigner/config.rb
262
244
  - lib/mysigner/credential_resolver.rb
263
245
  - lib/mysigner/export/exporter.rb
246
+ - lib/mysigner/formatting.rb
264
247
  - lib/mysigner/local_credentials.rb
265
248
  - lib/mysigner/signing/certificate_checker.rb
266
249
  - lib/mysigner/signing/gradle_signing_injector.rb
@@ -275,7 +258,6 @@ files:
275
258
  - lib/mysigner/upload/uploader.rb
276
259
  - lib/mysigner/version.rb
277
260
  - mysigner.gemspec
278
- - test_manual.rb
279
261
  homepage: https://mysigner.dev
280
262
  licenses:
281
263
  - Apache-2.0
data/.travis.yml DELETED
@@ -1,7 +0,0 @@
1
- ---
2
- sudo: false
3
- language: ruby
4
- cache: bundler
5
- rvm:
6
- - 2.6.10
7
- before_install: gem install bundler -v 1.17.2