mysigner 0.1.2 → 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 (47) 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/.rubocop.yml +55 -0
  6. data/.rubocop_todo.yml +112 -0
  7. data/CHANGELOG.md +96 -0
  8. data/Gemfile +5 -3
  9. data/Gemfile.lock +38 -8
  10. data/README.md +13 -15
  11. data/Rakefile +5 -3
  12. data/bin/console +4 -3
  13. data/bin/setup +3 -0
  14. data/exe/mysigner +2 -1
  15. data/lib/mysigner/build/android_executor.rb +46 -52
  16. data/lib/mysigner/build/android_parser.rb +33 -40
  17. data/lib/mysigner/build/configurator.rb +17 -16
  18. data/lib/mysigner/build/detector.rb +39 -50
  19. data/lib/mysigner/build/error_analyzer.rb +70 -68
  20. data/lib/mysigner/build/executor.rb +30 -37
  21. data/lib/mysigner/build/parser.rb +18 -18
  22. data/lib/mysigner/cli/auth_commands.rb +735 -752
  23. data/lib/mysigner/cli/build_commands.rb +697 -721
  24. data/lib/mysigner/cli/concerns/actionable_suggestions.rb +208 -154
  25. data/lib/mysigner/cli/concerns/api_helpers.rb +46 -54
  26. data/lib/mysigner/cli/concerns/error_handlers.rb +247 -237
  27. data/lib/mysigner/cli/concerns/helpers.rb +12 -1
  28. data/lib/mysigner/cli/diagnostic_commands.rb +659 -635
  29. data/lib/mysigner/cli/resource_commands.rb +880 -902
  30. data/lib/mysigner/cli/validate_commands.rb +25 -25
  31. data/lib/mysigner/cli.rb +3 -1
  32. data/lib/mysigner/client.rb +27 -19
  33. data/lib/mysigner/config.rb +93 -56
  34. data/lib/mysigner/export/exporter.rb +32 -36
  35. data/lib/mysigner/signing/certificate_checker.rb +18 -23
  36. data/lib/mysigner/signing/keystore_manager.rb +34 -39
  37. data/lib/mysigner/signing/validator.rb +38 -40
  38. data/lib/mysigner/signing/wizard.rb +329 -342
  39. data/lib/mysigner/upload/app_store_automation.rb +51 -49
  40. data/lib/mysigner/upload/app_store_submission.rb +87 -92
  41. data/lib/mysigner/upload/play_store_uploader.rb +98 -115
  42. data/lib/mysigner/upload/uploader.rb +101 -109
  43. data/lib/mysigner/version.rb +3 -1
  44. data/lib/mysigner.rb +13 -11
  45. data/mysigner.gemspec +36 -33
  46. data/test_manual.rb +37 -36
  47. metadata +37 -16
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Mysigner
2
4
  module Upload
3
5
  class AppStoreAutomation
@@ -11,23 +13,23 @@ module Mysigner
11
13
  def initialize(client:, organization_id:, opts: {})
12
14
  @client = client
13
15
  @organization_id = organization_id
14
- @wait_enabled = opts.key?(:wait) ? !!opts[:wait] : true
16
+ @wait_enabled = opts.key?(:wait) ? !opts[:wait].nil? : true
15
17
 
16
18
  poll = opts[:poll_interval] || opts[:poll_seconds]
17
19
  poll = poll.to_i if poll
18
- @poll_interval = poll && poll.positive? ? poll : DEFAULT_POLL_INTERVAL
20
+ @poll_interval = poll&.positive? ? poll : DEFAULT_POLL_INTERVAL
19
21
 
20
22
  timeout = opts[:timeout] || opts[:timeout_seconds]
21
23
  timeout = timeout.to_i if timeout
22
- @timeout = timeout && timeout.positive? ? timeout : DEFAULT_WAIT_TIMEOUT
24
+ @timeout = timeout&.positive? ? timeout : DEFAULT_WAIT_TIMEOUT
23
25
 
24
- @no_submit = !!opts[:no_submit]
26
+ @no_submit = !opts[:no_submit].nil?
25
27
  @now = opts[:now]
26
28
  end
27
29
 
28
30
  def perform!(metadata:, build_info:, metadata_overrides: {})
29
31
  build_info = symbolize_keys(build_info)
30
- metadata = metadata || {}
32
+ metadata ||= {}
31
33
 
32
34
  result = {
33
35
  wait: {
@@ -43,9 +45,9 @@ module Mysigner
43
45
  submission_source: nil
44
46
  }
45
47
 
46
- puts ""
47
- puts "🤖 App Store automation in progress..."
48
- puts ""
48
+ puts ''
49
+ puts '🤖 App Store automation in progress...'
50
+ puts ''
49
51
 
50
52
  app = ensure_app(build_info[:bundle_id])
51
53
  raise AutomationError, "App with bundle ID #{build_info[:bundle_id]} not found" unless app
@@ -56,11 +58,13 @@ module Mysigner
56
58
  unless build
57
59
  version_info = [build_info[:version], build_info[:build_number]].compact.join(' / ')
58
60
  version_info = "for #{version_info}" unless version_info.empty?
59
- raise AutomationError, "No processed build found #{version_info}. Upload a build first with 'mysigner ship appstore --wait'"
61
+ raise AutomationError,
62
+ "No processed build found #{version_info}. Upload a build first with 'mysigner ship appstore --wait'"
60
63
  end
61
64
 
62
65
  unless build_processed?(build)
63
- raise AutomationError, "Build #{build_info[:version]} (#{build_info[:build_number]}) is still processing. Wait for it or use --wait flag."
66
+ raise AutomationError,
67
+ "Build #{build_info[:version]} (#{build_info[:build_number]}) is still processing. Wait for it or use --wait flag."
64
68
  end
65
69
 
66
70
  version = ensure_app_store_version(app_id: app['id'], metadata: metadata, overrides: metadata_overrides)
@@ -70,12 +74,12 @@ module Mysigner
70
74
 
71
75
  if should_submit
72
76
  submit_for_review(
73
- version_id: version['id'],
77
+ version_id: version['id'],
74
78
  version_string: version['version_string'],
75
- metadata: metadata,
79
+ metadata: metadata,
76
80
  overrides: metadata_overrides
77
81
  )
78
- puts "✓ Submitted for App Store review"
82
+ puts '✓ Submitted for App Store review'
79
83
  result[:submitted] = true
80
84
  result[:submission_source] = submit_source
81
85
  else
@@ -83,8 +87,8 @@ module Mysigner
83
87
  result[:skip_reason] = skip_reason
84
88
  end
85
89
 
86
- puts ""
87
- puts "✅ App Store automation complete"
90
+ puts ''
91
+ puts '✅ App Store automation complete'
88
92
 
89
93
  result
90
94
  end
@@ -112,29 +116,29 @@ module Mysigner
112
116
 
113
117
  return [build, status] unless @wait_enabled
114
118
 
115
- puts "⏳ Waiting for Apple to finish processing the build..."
119
+ puts '⏳ Waiting for Apple to finish processing the build...'
116
120
  puts " Polling every #{@poll_interval}s (timeout #{@timeout}s)"
117
- print ""
121
+ print ''
118
122
 
119
123
  start_time = current_time
120
-
124
+
121
125
  loop do
122
126
  build = latest_build(app_id, build_info)
123
127
  status[:last_state] = build_state(build)
124
128
 
125
129
  if build && build_processed?(build)
126
130
  puts "\r✓ Build is processed and ready".ljust(70)
127
- puts ""
131
+ puts ''
128
132
  return [build, status]
129
133
  end
130
-
134
+
131
135
  elapsed = current_time - start_time
132
136
  status[:elapsed_seconds] = elapsed
133
137
 
134
138
  if elapsed >= @timeout
135
139
  status[:timed_out] = true
136
140
  puts "\r✗ Timed out after #{format_duration(elapsed)} (use --asc-timeout-seconds to extend)".ljust(90)
137
- puts ""
141
+ puts ''
138
142
  return [build, status]
139
143
  end
140
144
 
@@ -152,32 +156,30 @@ module Mysigner
152
156
  app_id: app_id,
153
157
  processed_only: !@wait_enabled
154
158
  }
155
-
159
+
156
160
  # Only filter by version/build if NOT using latest
157
161
  unless build_info[:use_latest]
158
162
  params[:version] = build_info[:version] if build_info[:version]
159
163
  params[:build_number] = build_info[:build_number] if build_info[:build_number]
160
164
  end
161
-
165
+
162
166
  # Apply min_build_number filter if set in release config
163
- if build_info[:min_build_number]
164
- params[:min_build_number] = build_info[:min_build_number]
165
- end
166
-
167
+ params[:min_build_number] = build_info[:min_build_number] if build_info[:min_build_number]
168
+
167
169
  response = @client.get(
168
170
  "/api/v1/organizations/#{@organization_id}/builds",
169
171
  params: params.compact
170
172
  )
171
173
 
172
174
  builds = Array(response[:data]['data']['builds'])
173
-
175
+
174
176
  # Smart build selection: filter by min_build_number client-side if API doesn't support it
175
177
  if build_info[:min_build_number] && builds.any?
176
178
  min_bn = build_info[:min_build_number].to_i
177
179
  builds = builds.select { |b| b['build_number'].to_i >= min_bn }
178
180
  end
179
-
180
- builds.first # Already ordered by uploaded_date desc
181
+
182
+ builds.first # Already ordered by uploaded_date desc
181
183
  rescue Mysigner::NotFoundError
182
184
  nil
183
185
  rescue StandardError => e
@@ -264,7 +266,7 @@ module Mysigner
264
266
  def determine_earliest_release_date(metadata, overrides)
265
267
  date = overrides['earliest_release_date'] || metadata['earliest_release_date']
266
268
  return nil unless date
267
-
269
+
268
270
  # Convert to ISO 8601 if not already
269
271
  date.respond_to?(:iso8601) ? date.iso8601 : date.to_s
270
272
  end
@@ -287,17 +289,17 @@ module Mysigner
287
289
 
288
290
  def determine_release_type(metadata, overrides)
289
291
  result = overrides['release_type'] || metadata['release_type']
290
-
292
+
291
293
  # FIX v7: Changed default from MANUAL to AFTER_APPROVAL
292
294
  # Log deprecation notice if no explicit release_type is set
293
295
  if result.nil?
294
296
  if ENV['MYSIGNER_DEBUG'] || @deprecation_warned.nil?
295
- puts " Note: Using default release_type AFTER_APPROVAL (was MANUAL in older versions)"
297
+ puts ' Note: Using default release_type AFTER_APPROVAL (was MANUAL in older versions)'
296
298
  @deprecation_warned = true
297
299
  end
298
300
  result = 'AFTER_APPROVAL'
299
301
  end
300
-
302
+
301
303
  result
302
304
  end
303
305
 
@@ -307,7 +309,7 @@ module Mysigner
307
309
  body: { build_id: build_id }
308
310
  )
309
311
 
310
- puts "✓ Attached build to App Store version"
312
+ puts '✓ Attached build to App Store version'
311
313
  rescue StandardError => e
312
314
  raise AutomationError, "Failed to attach build to version: #{e.message}"
313
315
  end
@@ -316,11 +318,13 @@ module Mysigner
316
318
  return [false, nil, '--no-auto-submit flag'] if @no_submit
317
319
 
318
320
  if overrides.key?('auto_submit')
319
- return overrides['auto_submit'] ? [true, 'CLI override', nil] : [false, nil, 'CLI override disabled auto_submit']
321
+ return overrides['auto_submit'] ? [true, 'CLI override',
322
+ nil] : [false, nil, 'CLI override disabled auto_submit']
320
323
  end
321
324
 
322
325
  if metadata.key?('auto_submit')
323
- return metadata['auto_submit'] ? [true, 'Dashboard configuration', nil] : [false, nil, 'Dashboard auto_submit disabled']
326
+ return metadata['auto_submit'] ? [true, 'Dashboard configuration',
327
+ nil] : [false, nil, 'Dashboard auto_submit disabled']
324
328
  end
325
329
 
326
330
  [false, nil, 'No auto_submit configuration']
@@ -329,27 +333,26 @@ module Mysigner
329
333
  def submit_for_review(version_id:, version_string: nil, metadata: {}, overrides: {})
330
334
  # Merge metadata and overrides
331
335
  merged = metadata.merge(overrides)
332
-
336
+
333
337
  # Get version string to check if first version
334
338
  version_string ||= merged['version_string'] || merged['version'] || '1.0'
335
339
  is_first_version = version_string.split('.').first.to_i <= 1
336
-
340
+
337
341
  # Validate required fields for Apple submission
338
342
  # Note: What's New is NOT required for version 1.0 (first release)
339
343
  # Support URL may already be set in App Store Connect, so we only warn if missing
340
344
  missing_fields = []
341
345
  missing_fields << "What's New text" if !is_first_version && merged['whats_new'].to_s.strip.empty?
342
-
346
+
343
347
  # Don't block on missing support_url - it may already be in App Store Connect
344
348
  # Just warn about it
345
- if merged['support_url'].to_s.strip.empty?
346
- puts "⚠️ Note: No Support URL provided via CLI - using value from App Store Connect if available"
347
- end
348
-
349
+ puts '⚠️ Note: No Support URL provided via CLI - using value from App Store Connect if available' if merged['support_url'].to_s.strip.empty?
350
+
349
351
  unless missing_fields.empty?
350
- raise AutomationError, "Cannot submit to Apple Store: missing required fields: #{missing_fields.join(', ')}. Please configure these in your My Signer dashboard or provide via --whats-new flag."
352
+ raise AutomationError,
353
+ "Cannot submit to Apple Store: missing required fields: #{missing_fields.join(', ')}. Please configure these in your My Signer dashboard or provide via --whats-new flag."
351
354
  end
352
-
355
+
353
356
  payload = {
354
357
  whats_new: merged['whats_new'],
355
358
  keywords: merged['keywords'],
@@ -357,7 +360,7 @@ module Mysigner
357
360
  promotional_text: merged['promotional_text'],
358
361
  support_url: merged['support_url'],
359
362
  locale: merged['locale']
360
- }.compact # Remove nil values
363
+ }.compact # Remove nil values
361
364
 
362
365
  @client.post(
363
366
  "/api/v1/organizations/#{@organization_id}/app_store_versions/#{version_id}/submit",
@@ -375,7 +378,6 @@ module Mysigner
375
378
  end
376
379
  end
377
380
 
378
-
379
381
  def format_duration(seconds)
380
382
  minutes = (seconds / 60).floor
381
383
  seconds = (seconds % 60).round
@@ -383,7 +385,7 @@ module Mysigner
383
385
  end
384
386
 
385
387
  def current_time
386
- (@now && @now.call) || Time.now
388
+ @now&.call || Time.now
387
389
  end
388
390
  end
389
391
  end
@@ -1,4 +1,4 @@
1
- require 'set'
1
+ # frozen_string_literal: true
2
2
 
3
3
  module Mysigner
4
4
  module Upload
@@ -16,52 +16,47 @@ module Mysigner
16
16
 
17
17
  # Submit build for App Store review
18
18
  def submit_for_review!(automation: nil)
19
- puts ""
20
- puts "📤 Preparing for App Store submission..."
21
- puts ""
22
-
19
+ puts ''
20
+ puts '📤 Preparing for App Store submission...'
21
+ puts ''
22
+
23
23
  begin
24
24
  # Step 1: Fetch release metadata from My Signer API
25
25
  merged = merge_metadata(fetch_release_metadata)
26
26
  metadata = merged[:merged]
27
-
28
- if metadata && metadata.any?
29
- puts "✓ Loaded release configuration from My Signer"
30
- puts ""
27
+
28
+ if metadata&.any?
29
+ puts '✓ Loaded release configuration from My Signer'
30
+ puts ''
31
31
  display_metadata(metadata)
32
32
  else
33
- puts "⚠️ No release configuration found in My Signer"
33
+ puts '⚠️ No release configuration found in My Signer'
34
34
  puts " Create one at: #{@client.api_url}/organizations/#{@organization_id}/app_store_releases"
35
- puts ""
35
+ puts ''
36
36
  end
37
-
37
+
38
38
  # Enrich build_info with config values for smart build selection
39
39
  enriched_build_info = symbolize_keys(@build_info)
40
40
  if metadata
41
41
  # min_build_number: skip builds below this number
42
- if metadata['build_number'] && !enriched_build_info[:build_number]
43
- enriched_build_info[:min_build_number] = metadata['build_number'].to_i
44
- end
42
+ enriched_build_info[:min_build_number] = metadata['build_number'].to_i if metadata['build_number'] && !enriched_build_info[:build_number]
45
43
  # Use version_string from config if not specified
46
- if metadata['version_string'] && !enriched_build_info[:version]
47
- enriched_build_info[:version] = metadata['version_string']
48
- end
44
+ enriched_build_info[:version] = metadata['version_string'] if metadata['version_string'] && !enriched_build_info[:version]
49
45
  end
50
-
46
+
51
47
  automation_result = if automation
52
- automation.perform!(
53
- metadata: metadata,
54
- build_info: enriched_build_info,
55
- metadata_overrides: @metadata_overrides
56
- )
57
- else
58
- guide_to_manual_submission(metadata)
59
- nil
60
- end
48
+ automation.perform!(
49
+ metadata: metadata,
50
+ build_info: enriched_build_info,
51
+ metadata_overrides: @metadata_overrides
52
+ )
53
+ else
54
+ guide_to_manual_submission(metadata)
55
+ nil
56
+ end
61
57
 
62
58
  { success: true, metadata: metadata, automation: automation_result }
63
-
64
- rescue => e
59
+ rescue StandardError => e
65
60
  raise SubmissionError, "Failed to prepare submission: #{e.message}"
66
61
  end
67
62
  end
@@ -70,40 +65,37 @@ module Mysigner
70
65
 
71
66
  def fetch_release_metadata
72
67
  # Fetch release metadata from My Signer API
73
- begin
74
- response = @client.get(
75
- "/api/v1/organizations/#{@organization_id}/app_store_releases",
76
- params: { bundle_id: @build_info[:bundle_id] }
77
- )
78
-
79
- if response[:success]
80
- data = response[:data]
81
- # API returns { app_store_releases: [...] } - extract first release
82
- if data.is_a?(Hash) && data['app_store_releases'].is_a?(Array)
83
- data['app_store_releases'].first
84
- elsif data.is_a?(Hash) && data['app_store_release']
85
- # Single release format
86
- data['app_store_release']
87
- else
88
- data
89
- end
68
+
69
+ response = @client.get(
70
+ "/api/v1/organizations/#{@organization_id}/app_store_releases",
71
+ params: { bundle_id: @build_info[:bundle_id] }
72
+ )
73
+
74
+ if response[:success]
75
+ data = response[:data]
76
+ # API returns { app_store_releases: [...] } - extract first release
77
+ if data.is_a?(Hash) && data['app_store_releases'].is_a?(Array)
78
+ data['app_store_releases'].first
79
+ elsif data.is_a?(Hash) && data['app_store_release']
80
+ # Single release format
81
+ data['app_store_release']
90
82
  else
91
- nil
83
+ data
92
84
  end
93
- rescue Mysigner::NotFoundError
94
- # No configuration found - that's okay
95
- nil
96
- rescue StandardError => e
97
- puts "⚠️ Could not fetch release metadata: #{e.message}"
98
- nil
99
85
  end
86
+ rescue Mysigner::NotFoundError
87
+ # No configuration found - that's okay
88
+ nil
89
+ rescue StandardError => e
90
+ puts "⚠️ Could not fetch release metadata: #{e.message}"
91
+ nil
100
92
  end
101
93
 
102
94
  # Fetch release config and extract min_build_number for smart build selection
103
95
  def fetch_release_config
104
96
  metadata = fetch_release_metadata
105
97
  return {} unless metadata
106
-
98
+
107
99
  config = {}
108
100
  config[:min_build_number] = metadata['build_number'].to_i if metadata['build_number']
109
101
  config[:release_type] = metadata['release_type'] if metadata['release_type']
@@ -122,39 +114,40 @@ module Mysigner
122
114
  end
123
115
 
124
116
  def guide_to_manual_submission(metadata)
125
- puts "📋 Next Steps for App Store Submission"
126
- puts "=" * 60
127
- puts ""
128
- puts "Your build is uploaded to App Store Connect!"
129
- puts ""
130
- puts "To submit for review:"
131
- puts " 1. Wait for Apple to process the build (5-15 minutes)"
132
- puts " 2. Open App Store Connect:"
133
- puts " https://appstoreconnect.apple.com"
117
+ puts '📋 Next Steps for App Store Submission'
118
+ puts '=' * 60
119
+ puts ''
120
+ puts 'Your build is uploaded to App Store Connect!'
121
+ puts ''
122
+ puts 'To submit for review:'
123
+ puts ' 1. Wait for Apple to process the build (5-15 minutes)'
124
+ puts ' 2. Open App Store Connect:'
125
+ puts ' https://appstoreconnect.apple.com'
134
126
  puts " 3. Select your app and go to 'App Store' tab"
135
- puts " 4. Create a new version or select existing one"
127
+ puts ' 4. Create a new version or select existing one'
136
128
  puts " 5. Select this build (#{@build_info[:version]} / #{@build_info[:build_number]})"
137
- puts " 6. Add screenshots and metadata if not already present"
129
+ puts ' 6. Add screenshots and metadata if not already present'
138
130
  puts " 7. Click 'Submit for Review'"
139
- puts ""
131
+ puts ''
140
132
 
141
- if metadata && metadata['auto_submit']
142
- puts "💡 Auto-submit enabled"
143
- end
133
+ puts '💡 Auto-submit enabled' if metadata && metadata['auto_submit']
144
134
 
145
- puts ""
146
- puts "Tip: rerun with --submit-for-review when ready"
147
- puts " Use --wait/--asc-timeout-seconds to control polling"
148
- puts ""
135
+ puts ''
136
+ puts 'Tip: rerun with --submit-for-review when ready'
137
+ puts ' Use --wait/--asc-timeout-seconds to control polling'
138
+ puts ''
149
139
  end
150
140
 
151
141
  def display_metadata(metadata)
152
- puts "📝 Release Configuration:"
142
+ puts '📝 Release Configuration:'
153
143
  print_metadata_line('Bundle ID', metadata['bundle_identifier'], 'bundle_identifier')
154
144
  print_metadata_line('App Name', metadata['app_name'], 'app_name') if metadata['app_name']
155
-
145
+
156
146
  # Version info from config
157
- print_metadata_line('Version String', metadata['version_string'], 'version_string') if metadata['version_string']
147
+ if metadata['version_string']
148
+ print_metadata_line('Version String', metadata['version_string'],
149
+ 'version_string')
150
+ end
158
151
  print_metadata_line('Min Build #', metadata['build_number'], 'build_number') if metadata['build_number']
159
152
 
160
153
  if metadata['whats_new'] && !metadata['whats_new'].to_s.strip.empty?
@@ -162,7 +155,7 @@ module Mysigner
162
155
  else
163
156
  puts " What's New: —#{override_suffix('whats_new')}"
164
157
  end
165
-
158
+
166
159
  if metadata['promotional_text'] && !metadata['promotional_text'].to_s.strip.empty?
167
160
  puts " Promo Text: #{truncate(metadata['promotional_text'])}#{override_suffix('promotional_text')}"
168
161
  end
@@ -170,14 +163,14 @@ module Mysigner
170
163
  print_metadata_line('Support URL', metadata['support_url'], 'support_url')
171
164
  print_metadata_line('Marketing URL', metadata['marketing_url'], 'marketing_url')
172
165
  print_metadata_line('Privacy URL', metadata['privacy_policy_url'], 'privacy_policy_url')
173
-
166
+
174
167
  # Release settings
175
168
  release_type_label = format_release_type(metadata['release_type'])
176
169
  print_metadata_line('Release Type', release_type_label, 'release_type')
177
170
  if metadata['release_type'] == 'SCHEDULED' && metadata['earliest_release_date']
178
171
  print_metadata_line('Scheduled Date', metadata['earliest_release_date'], 'earliest_release_date')
179
172
  end
180
-
173
+
181
174
  print_metadata_toggle('Auto-submit', metadata['auto_submit'], 'auto_submit')
182
175
  print_metadata_toggle('Phased Release', metadata['phased_release'], 'phased_release')
183
176
 
@@ -188,9 +181,9 @@ module Mysigner
188
181
  end
189
182
 
190
183
  warn_missing_submission_fields(metadata)
191
- puts ""
184
+ puts ''
192
185
  end
193
-
186
+
194
187
  def format_release_type(release_type)
195
188
  case release_type
196
189
  when 'AFTER_APPROVAL' then 'After Approval (auto-release)'
@@ -205,10 +198,10 @@ module Mysigner
205
198
 
206
199
  overrides.each do |key, value|
207
200
  merged[key] = if merged[key].is_a?(Hash) && value.is_a?(Hash)
208
- deep_merge(merged[key], value)
209
- else
210
- value
211
- end
201
+ deep_merge(merged[key], value)
202
+ else
203
+ value
204
+ end
212
205
  end
213
206
 
214
207
  merged
@@ -253,7 +246,11 @@ module Mysigner
253
246
  end
254
247
 
255
248
  def print_metadata_toggle(label, value, key)
256
- human = value.nil? ? '—' : (value ? 'Yes' : 'No')
249
+ human = if value.nil?
250
+ '—'
251
+ else
252
+ (value ? 'Yes' : 'No')
253
+ end
257
254
  puts " #{label}: #{human}#{override_suffix(key)}"
258
255
  end
259
256
 
@@ -294,19 +291,17 @@ module Mysigner
294
291
  warnings = []
295
292
  # What's New only required for updates (version > 1.0)
296
293
  warnings << "Missing What's New copy (required for version updates)" if !is_first_version && metadata['whats_new'].to_s.strip.empty?
297
-
294
+
298
295
  # Support URL may already be in App Store Connect, so just note it
299
296
  if metadata['support_url'].to_s.strip.empty?
300
- warnings << "Support URL not configured in My Signer (will use App Store Connect value if available)"
297
+ warnings << 'Support URL not configured in My Signer (will use App Store Connect value if available)'
301
298
  end
302
299
 
303
300
  return if warnings.empty?
304
301
 
305
- puts " ⚠️ Notes:" unless warnings.empty?
302
+ puts ' ⚠️ Notes:' unless warnings.empty?
306
303
  warnings.each { |msg| puts " - #{msg}" }
307
304
  end
308
305
  end
309
306
  end
310
307
  end
311
-
312
-