mysigner 0.1.2 → 0.1.4

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 (56) 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 +4 -0
  6. data/.rubocop.yml +55 -0
  7. data/.rubocop_todo.yml +126 -0
  8. data/CHANGELOG.md +96 -0
  9. data/Gemfile +5 -3
  10. data/Gemfile.lock +38 -8
  11. data/README.md +14 -16
  12. data/Rakefile +5 -3
  13. data/bin/console +4 -3
  14. data/bin/setup +3 -0
  15. data/certificate_.cer +0 -0
  16. data/exe/mysigner +19 -2
  17. data/iOS_App_Store_Profile.mobileprovision +1 -0
  18. data/iOS_Distribution_Certificate.cer +1 -0
  19. data/lib/mysigner/build/android_executor.rb +83 -63
  20. data/lib/mysigner/build/android_parser.rb +33 -40
  21. data/lib/mysigner/build/configurator.rb +17 -16
  22. data/lib/mysigner/build/detector.rb +39 -50
  23. data/lib/mysigner/build/error_analyzer.rb +70 -68
  24. data/lib/mysigner/build/executor.rb +30 -37
  25. data/lib/mysigner/build/parser.rb +18 -18
  26. data/lib/mysigner/cleanup/private_keys_purger.rb +41 -0
  27. data/lib/mysigner/cli/auth_commands.rb +771 -764
  28. data/lib/mysigner/cli/build_commands.rb +962 -796
  29. data/lib/mysigner/cli/concerns/actionable_suggestions.rb +208 -154
  30. data/lib/mysigner/cli/concerns/api_helpers.rb +46 -54
  31. data/lib/mysigner/cli/concerns/error_handlers.rb +247 -237
  32. data/lib/mysigner/cli/concerns/helpers.rb +44 -1
  33. data/lib/mysigner/cli/diagnostic_commands.rb +667 -636
  34. data/lib/mysigner/cli/resource_commands.rb +1153 -985
  35. data/lib/mysigner/cli/validate_commands.rb +25 -25
  36. data/lib/mysigner/cli.rb +11 -1
  37. data/lib/mysigner/client.rb +27 -19
  38. data/lib/mysigner/config.rb +161 -60
  39. data/lib/mysigner/export/exporter.rb +38 -37
  40. data/lib/mysigner/signing/certificate_checker.rb +18 -23
  41. data/lib/mysigner/signing/gradle_signing_injector.rb +67 -0
  42. data/lib/mysigner/signing/keystore_manager.rb +81 -61
  43. data/lib/mysigner/signing/validator.rb +38 -40
  44. data/lib/mysigner/signing/wizard.rb +329 -342
  45. data/lib/mysigner/upload/app_store_automation.rb +96 -49
  46. data/lib/mysigner/upload/app_store_submission.rb +87 -92
  47. data/lib/mysigner/upload/asc_rest_uploader.rb +119 -0
  48. data/lib/mysigner/upload/play_store_uploader.rb +164 -144
  49. data/lib/mysigner/upload/uploader.rb +136 -115
  50. data/lib/mysigner/version.rb +3 -1
  51. data/lib/mysigner.rb +13 -11
  52. data/mysigner.gemspec +36 -33
  53. data/profile_.mobileprovision +0 -0
  54. data/test_manual.rb +37 -36
  55. metadata +44 -17
  56. data/.DS_Store +0 -0
@@ -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,33 @@ 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
+ # Proper boolean coercion — `!x.nil?` evaluates to `true` for `false`,
17
+ # which previously caused `wait: false` / `no_submit: false` to be
18
+ # treated as `true`. Use `!!` to get the actual boolean semantics.
19
+ @wait_enabled = opts.key?(:wait) ? !opts[:wait].nil? : true
15
20
 
16
21
  poll = opts[:poll_interval] || opts[:poll_seconds]
17
22
  poll = poll.to_i if poll
18
- @poll_interval = poll && poll.positive? ? poll : DEFAULT_POLL_INTERVAL
23
+ @poll_interval = poll&.positive? ? poll : DEFAULT_POLL_INTERVAL
19
24
 
20
25
  timeout = opts[:timeout] || opts[:timeout_seconds]
21
26
  timeout = timeout.to_i if timeout
22
- @timeout = timeout && timeout.positive? ? timeout : DEFAULT_WAIT_TIMEOUT
27
+ @timeout = timeout&.positive? ? timeout : DEFAULT_WAIT_TIMEOUT
28
+
29
+ @no_submit = !opts[:no_submit].nil?
30
+
31
+ # Whether to submit by default when NEITHER cli_defaults nor CLI
32
+ # overrides specify `auto_submit`. `ship`/`submit` pass true (it's
33
+ # the user's explicit intent). Dashboard `cli_defaults.auto_submit`
34
+ # still wins if set — we only fall back to this when silent.
35
+ @default_submit = opts.key?(:default_submit) ? !opts[:default_submit].nil? : false
23
36
 
24
- @no_submit = !!opts[:no_submit]
25
37
  @now = opts[:now]
26
38
  end
27
39
 
28
40
  def perform!(metadata:, build_info:, metadata_overrides: {})
29
41
  build_info = symbolize_keys(build_info)
30
- metadata = metadata || {}
42
+ metadata ||= {}
31
43
 
32
44
  result = {
33
45
  wait: {
@@ -43,9 +55,9 @@ module Mysigner
43
55
  submission_source: nil
44
56
  }
45
57
 
46
- puts ""
47
- puts "🤖 App Store automation in progress..."
48
- puts ""
58
+ puts ''
59
+ puts '🤖 App Store automation in progress...'
60
+ puts ''
49
61
 
50
62
  app = ensure_app(build_info[:bundle_id])
51
63
  raise AutomationError, "App with bundle ID #{build_info[:bundle_id]} not found" unless app
@@ -56,11 +68,13 @@ module Mysigner
56
68
  unless build
57
69
  version_info = [build_info[:version], build_info[:build_number]].compact.join(' / ')
58
70
  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'"
71
+ raise AutomationError,
72
+ "No processed build found #{version_info}. Upload a build first with 'mysigner ship appstore --wait'"
60
73
  end
61
74
 
62
75
  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."
76
+ raise AutomationError,
77
+ "Build #{build_info[:version]} (#{build_info[:build_number]}) is still processing. Wait for it or use --wait flag."
64
78
  end
65
79
 
66
80
  version = ensure_app_store_version(app_id: app['id'], metadata: metadata, overrides: metadata_overrides)
@@ -70,12 +84,12 @@ module Mysigner
70
84
 
71
85
  if should_submit
72
86
  submit_for_review(
73
- version_id: version['id'],
87
+ version_id: version['id'],
74
88
  version_string: version['version_string'],
75
- metadata: metadata,
89
+ metadata: metadata,
76
90
  overrides: metadata_overrides
77
91
  )
78
- puts "✓ Submitted for App Store review"
92
+ puts '✓ Submitted for App Store review'
79
93
  result[:submitted] = true
80
94
  result[:submission_source] = submit_source
81
95
  else
@@ -83,8 +97,8 @@ module Mysigner
83
97
  result[:skip_reason] = skip_reason
84
98
  end
85
99
 
86
- puts ""
87
- puts "✅ App Store automation complete"
100
+ puts ''
101
+ puts '✅ App Store automation complete'
88
102
 
89
103
  result
90
104
  end
@@ -112,29 +126,29 @@ module Mysigner
112
126
 
113
127
  return [build, status] unless @wait_enabled
114
128
 
115
- puts "⏳ Waiting for Apple to finish processing the build..."
129
+ puts '⏳ Waiting for Apple to finish processing the build...'
116
130
  puts " Polling every #{@poll_interval}s (timeout #{@timeout}s)"
117
- print ""
131
+ print ''
118
132
 
119
133
  start_time = current_time
120
-
134
+
121
135
  loop do
122
136
  build = latest_build(app_id, build_info)
123
137
  status[:last_state] = build_state(build)
124
138
 
125
139
  if build && build_processed?(build)
126
140
  puts "\r✓ Build is processed and ready".ljust(70)
127
- puts ""
141
+ puts ''
128
142
  return [build, status]
129
143
  end
130
-
144
+
131
145
  elapsed = current_time - start_time
132
146
  status[:elapsed_seconds] = elapsed
133
147
 
134
148
  if elapsed >= @timeout
135
149
  status[:timed_out] = true
136
150
  puts "\r✗ Timed out after #{format_duration(elapsed)} (use --asc-timeout-seconds to extend)".ljust(90)
137
- puts ""
151
+ puts ''
138
152
  return [build, status]
139
153
  end
140
154
 
@@ -152,32 +166,30 @@ module Mysigner
152
166
  app_id: app_id,
153
167
  processed_only: !@wait_enabled
154
168
  }
155
-
169
+
156
170
  # Only filter by version/build if NOT using latest
157
171
  unless build_info[:use_latest]
158
172
  params[:version] = build_info[:version] if build_info[:version]
159
173
  params[:build_number] = build_info[:build_number] if build_info[:build_number]
160
174
  end
161
-
175
+
162
176
  # 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
-
177
+ params[:min_build_number] = build_info[:min_build_number] if build_info[:min_build_number]
178
+
167
179
  response = @client.get(
168
180
  "/api/v1/organizations/#{@organization_id}/builds",
169
181
  params: params.compact
170
182
  )
171
183
 
172
184
  builds = Array(response[:data]['data']['builds'])
173
-
185
+
174
186
  # Smart build selection: filter by min_build_number client-side if API doesn't support it
175
187
  if build_info[:min_build_number] && builds.any?
176
188
  min_bn = build_info[:min_build_number].to_i
177
189
  builds = builds.select { |b| b['build_number'].to_i >= min_bn }
178
190
  end
179
-
180
- builds.first # Already ordered by uploaded_date desc
191
+
192
+ builds.first # Already ordered by uploaded_date desc
181
193
  rescue Mysigner::NotFoundError
182
194
  nil
183
195
  rescue StandardError => e
@@ -264,7 +276,7 @@ module Mysigner
264
276
  def determine_earliest_release_date(metadata, overrides)
265
277
  date = overrides['earliest_release_date'] || metadata['earliest_release_date']
266
278
  return nil unless date
267
-
279
+
268
280
  # Convert to ISO 8601 if not already
269
281
  date.respond_to?(:iso8601) ? date.iso8601 : date.to_s
270
282
  end
@@ -287,17 +299,17 @@ module Mysigner
287
299
 
288
300
  def determine_release_type(metadata, overrides)
289
301
  result = overrides['release_type'] || metadata['release_type']
290
-
302
+
291
303
  # FIX v7: Changed default from MANUAL to AFTER_APPROVAL
292
304
  # Log deprecation notice if no explicit release_type is set
293
305
  if result.nil?
294
306
  if ENV['MYSIGNER_DEBUG'] || @deprecation_warned.nil?
295
- puts " Note: Using default release_type AFTER_APPROVAL (was MANUAL in older versions)"
307
+ puts ' Note: Using default release_type AFTER_APPROVAL (was MANUAL in older versions)'
296
308
  @deprecation_warned = true
297
309
  end
298
310
  result = 'AFTER_APPROVAL'
299
311
  end
300
-
312
+
301
313
  result
302
314
  end
303
315
 
@@ -307,7 +319,7 @@ module Mysigner
307
319
  body: { build_id: build_id }
308
320
  )
309
321
 
310
- puts "✓ Attached build to App Store version"
322
+ puts '✓ Attached build to App Store version'
311
323
  rescue StandardError => e
312
324
  raise AutomationError, "Failed to attach build to version: #{e.message}"
313
325
  end
@@ -315,49 +327,85 @@ module Mysigner
315
327
  def should_submit_with_reason(metadata, overrides)
316
328
  return [false, nil, '--no-auto-submit flag'] if @no_submit
317
329
 
330
+ # Precedence: explicit CLI flag > dashboard cli_defaults > command default.
331
+ # The command default is set by `ship appstore` / `submit` (true),
332
+ # preserving backward-compat when the user runs those commands without
333
+ # configuring anything. Dashboard `auto_submit: false` now correctly
334
+ # suppresses submission (previously hard-overridden at the call site).
318
335
  if overrides.key?('auto_submit')
319
- return overrides['auto_submit'] ? [true, 'CLI override', nil] : [false, nil, 'CLI override disabled auto_submit']
336
+ return overrides['auto_submit'] ? [true, 'CLI override',
337
+ nil] : [false, nil, 'CLI override disabled auto_submit']
320
338
  end
321
339
 
322
340
  if metadata.key?('auto_submit')
323
- return metadata['auto_submit'] ? [true, 'Dashboard configuration', nil] : [false, nil, 'Dashboard auto_submit disabled']
341
+ return metadata['auto_submit'] ? [true, 'Dashboard configuration',
342
+ nil] : [false, nil, 'Dashboard auto_submit disabled']
324
343
  end
325
344
 
345
+ return [true, 'command default', nil] if @default_submit
346
+
326
347
  [false, nil, 'No auto_submit configuration']
327
348
  end
328
349
 
329
350
  def submit_for_review(version_id:, version_string: nil, metadata: {}, overrides: {})
330
351
  # Merge metadata and overrides
331
352
  merged = metadata.merge(overrides)
332
-
353
+
333
354
  # Get version string to check if first version
334
355
  version_string ||= merged['version_string'] || merged['version'] || '1.0'
335
356
  is_first_version = version_string.split('.').first.to_i <= 1
336
-
357
+
337
358
  # Validate required fields for Apple submission
338
359
  # Note: What's New is NOT required for version 1.0 (first release)
339
360
  # Support URL may already be set in App Store Connect, so we only warn if missing
340
361
  missing_fields = []
341
362
  missing_fields << "What's New text" if !is_first_version && merged['whats_new'].to_s.strip.empty?
342
-
363
+
343
364
  # Don't block on missing support_url - it may already be in App Store Connect
344
365
  # 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
-
366
+ puts '⚠️ Note: No Support URL provided via CLI - using value from App Store Connect if available' if merged['support_url'].to_s.strip.empty?
367
+
349
368
  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."
369
+ raise AutomationError,
370
+ "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
371
  end
352
-
372
+
373
+ # Primary-locale fields (backward-compatible path): backend will
374
+ # upsert a single localization for the app's primary locale when
375
+ # these top-level keys are present.
353
376
  payload = {
354
377
  whats_new: merged['whats_new'],
355
378
  keywords: merged['keywords'],
356
379
  marketing_url: merged['marketing_url'],
357
380
  promotional_text: merged['promotional_text'],
358
381
  support_url: merged['support_url'],
382
+ description: merged['description'],
359
383
  locale: merged['locale']
360
- }.compact # Remove nil values
384
+ }.compact
385
+
386
+ # Multi-locale path: if cli_defaults includes a `localizations` array,
387
+ # forward it so the backend can PATCH/POST each locale on ASC
388
+ # (appStoreVersionLocalizations) instead of only the primary.
389
+ if merged['localizations'].is_a?(Array) && merged['localizations'].any?
390
+ payload[:localizations] = merged['localizations'].map do |loc|
391
+ next nil unless loc.is_a?(Hash)
392
+
393
+ {
394
+ locale: loc['locale'] || loc[:locale],
395
+ whats_new: loc['whats_new'] || loc[:whats_new],
396
+ keywords: loc['keywords'] || loc[:keywords],
397
+ marketing_url: loc['marketing_url'] || loc[:marketing_url],
398
+ promotional_text: loc['promotional_text'] || loc[:promotional_text],
399
+ support_url: loc['support_url'] || loc[:support_url],
400
+ description: loc['description'] || loc[:description]
401
+ }.compact
402
+ end.compact
403
+ end
404
+
405
+ # Phased release: backend flips @version.phased_release_pending and
406
+ # enqueues PhasedReleaseActivationJob, which POSTs
407
+ # appStoreVersionPhasedReleases after Apple approves the submission.
408
+ payload[:phased_release] = true if merged['phased_release']
361
409
 
362
410
  @client.post(
363
411
  "/api/v1/organizations/#{@organization_id}/app_store_versions/#{version_id}/submit",
@@ -375,7 +423,6 @@ module Mysigner
375
423
  end
376
424
  end
377
425
 
378
-
379
426
  def format_duration(seconds)
380
427
  minutes = (seconds / 60).floor
381
428
  seconds = (seconds % 60).round
@@ -383,7 +430,7 @@ module Mysigner
383
430
  end
384
431
 
385
432
  def current_time
386
- (@now && @now.call) || Time.now
433
+ @now&.call || Time.now
387
434
  end
388
435
  end
389
436
  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
-