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,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Mysigner
2
4
  class CLI < Thor
3
5
  module ResourceCommands
4
6
  def self.included(base)
5
7
  base.class_eval do
6
- desc "devices", "List registered test devices (UDIDs)"
8
+ desc 'devices', 'List registered test devices (UDIDs)'
7
9
  method_option :platform, type: :string, aliases: '-p', desc: 'Filter by platform (IOS, MAC_OS, TV_OS)'
8
10
  method_option :status, type: :string, aliases: '-s', desc: 'Filter by status (ENABLED, DISABLED)'
9
11
  method_option :search, type: :string, aliases: '-q', desc: 'Search by name or UDID'
@@ -13,8 +15,8 @@ module Mysigner
13
15
  config = load_config
14
16
  client = create_client(config)
15
17
 
16
- say "📱 Devices", :cyan
17
- say ""
18
+ say '📱 Devices', :cyan
19
+ say ''
18
20
 
19
21
  # Build query params
20
22
  params = {
@@ -31,8 +33,8 @@ module Mysigner
31
33
  pagination = response[:data]['pagination']
32
34
 
33
35
  if devices.empty?
34
- say "No devices found", :yellow
35
- say ""
36
+ say 'No devices found', :yellow
37
+ say ''
36
38
  say "Tip: Register a device with 'mysigner device add NAME UDID'", :yellow
37
39
  return
38
40
  end
@@ -41,21 +43,19 @@ module Mysigner
41
43
  devices.each do |device|
42
44
  status_icon = device['status'] == 'ENABLED' ? '✓' : '✗'
43
45
  status_color = device['status'] == 'ENABLED' ? :green : :red
44
-
46
+
45
47
  say " #{status_icon} #{device['name']} (ID: #{device['id']})", status_color
46
48
  say " UDID: #{device['udid']}"
47
49
  say " Platform: #{device['platform']} | Class: #{device['device_class']}"
48
50
  say " Status: #{device['status']}"
49
- say ""
51
+ say ''
50
52
  end
51
53
 
52
54
  # Show pagination
53
55
  if pagination
54
56
  say "Page #{pagination['page']} of #{pagination['total_pages']} (#{pagination['total']} total)", :yellow
55
57
 
56
- if pagination['page'] < pagination['total_pages']
57
- say "Run with --page #{pagination['page'] + 1} to see more", :yellow
58
- end
58
+ say "Run with --page #{pagination['page'] + 1} to see more", :yellow if pagination['page'] < pagination['total_pages']
59
59
  end
60
60
  rescue Mysigner::ClientError => e
61
61
  error "Failed to fetch devices: #{e.message}"
@@ -63,57 +63,57 @@ module Mysigner
63
63
  end
64
64
  end
65
65
 
66
- desc "device SUBCOMMAND", "Manage test devices (detect, add, update)"
66
+ desc 'device SUBCOMMAND', 'Manage test devices (detect, add, update)'
67
67
  long_desc <<~DESC
68
68
  Register and manage test devices for development builds.
69
-
69
+
70
70
  WHY REGISTER DEVICES?
71
-
71
+
72
72
  To install development/adhoc builds on physical devices, you must register
73
73
  their UDIDs (Unique Device Identifiers) with Apple and include them in your
74
74
  provisioning profiles.
75
-
75
+
76
76
  SUBCOMMANDS:
77
-
77
+
78
78
  mysigner device detect
79
79
  Auto-detect connected iOS devices and show their UDIDs
80
-
80
+ #{' '}
81
81
  mysigner device add NAME UDID [--platform IOS]
82
82
  Register a new device for testing
83
-
83
+ #{' '}
84
84
  mysigner device update ID NEW_NAME
85
85
  Rename an existing device
86
-
86
+
87
87
  HOW TO GET A DEVICE UDID:
88
-
88
+
89
89
  Method 1 - Auto-detect (Recommended):
90
90
  mysigner device detect
91
-
91
+ #{' '}
92
92
  This will find all connected iOS devices and let you register them
93
93
  interactively. No need to open any other apps.
94
-
94
+
95
95
  Method 2 - Via Finder:
96
96
  1. Connect your iPhone/iPad to your Mac
97
97
  2. Open Finder and select your device in the sidebar
98
98
  3. Click on the device info to reveal UDID
99
99
  4. Right-click → Copy UDID
100
-
100
+
101
101
  EXAMPLES:
102
-
102
+
103
103
  # Register your iPhone
104
104
  mysigner device add "My iPhone 15" 00008030-001A1B2C3D4E567F
105
-
105
+ #{' '}
106
106
  # Register an iPad
107
107
  mysigner device add "Test iPad" da83bb40dba39e35d258988d856508798db7afba
108
-
108
+ #{' '}
109
109
  # Register a Mac for Mac Catalyst apps
110
110
  mysigner device add "MacBook Pro" ABC123... --platform MAC_OS
111
-
111
+ #{' '}
112
112
  # Rename a device (use ID from 'mysigner devices' list)
113
113
  mysigner device update 42 "John's iPhone"
114
-
114
+
115
115
  NOTES:
116
-
116
+
117
117
  • UDIDs are 40 hex characters (0-9, a-f) or 25 characters for newer devices
118
118
  • You can register up to 100 devices per year per account
119
119
  • After registering, regenerate your provisioning profiles to include the device
@@ -129,14 +129,14 @@ module Mysigner
129
129
  detect_connected_devices(config, client)
130
130
  when 'add'
131
131
  if args.length < 2
132
- error "Usage: mysigner device add NAME UDID [--platform IOS]"
133
- say ""
134
- say "Example: mysigner device add \"My iPhone\" 00008030-001A1B2C3D4E567F", :yellow
135
- say ""
132
+ error 'Usage: mysigner device add NAME UDID [--platform IOS]'
133
+ say ''
134
+ say 'Example: mysigner device add "My iPhone" 00008030-001A1B2C3D4E567F', :yellow
135
+ say ''
136
136
  say "💡 Don't know your UDID? Run:", :cyan
137
- say " mysigner device detect", :cyan
138
- say ""
139
- say " This will auto-detect connected devices and let you register them.", :cyan
137
+ say ' mysigner device detect', :cyan
138
+ say ''
139
+ say ' This will auto-detect connected devices and let you register them.', :cyan
140
140
  exit 1
141
141
  end
142
142
 
@@ -144,8 +144,8 @@ module Mysigner
144
144
  udid = args[1]
145
145
  platform = options[:platform].upcase
146
146
 
147
- say "📱 Registering device...", :cyan
148
- say ""
147
+ say '📱 Registering device...', :cyan
148
+ say ''
149
149
 
150
150
  begin
151
151
  response = client.post(
@@ -158,15 +158,15 @@ module Mysigner
158
158
  )
159
159
 
160
160
  device = response[:data]['device']
161
- say "✓ Device registered successfully!", :green
162
- say ""
163
- say "Details:", :bold
161
+ say '✓ Device registered successfully!', :green
162
+ say ''
163
+ say 'Details:', :bold
164
164
  say " Name: #{device['name']}"
165
165
  say " UDID: #{device['udid']}"
166
166
  say " Platform: #{device['platform']}"
167
167
  say " Status: #{device['status']}"
168
168
  rescue Mysigner::ValidationError => e
169
- error "Validation failed:"
169
+ error 'Validation failed:'
170
170
  if e.details
171
171
  e.details.each do |field, errors|
172
172
  say " #{field}: #{errors.join(', ')}", :red
@@ -177,8 +177,8 @@ module Mysigner
177
177
  say " Suggestion: #{e.suggestion}", :yellow if e.suggestion
178
178
  exit 1
179
179
  rescue Mysigner::ClientError => e
180
- if e.message.include?("already exists")
181
- error "Device with this UDID already exists"
180
+ if e.message.include?('already exists')
181
+ error 'Device with this UDID already exists'
182
182
  else
183
183
  error "Failed to register device: #{e.message}"
184
184
  end
@@ -186,11 +186,11 @@ module Mysigner
186
186
  end
187
187
  when 'update'
188
188
  if args.length < 2
189
- error "Usage: mysigner device update ID NEW_NAME"
190
- say ""
189
+ error 'Usage: mysigner device update ID NEW_NAME'
190
+ say ''
191
191
  say "Example: mysigner device update 42 \"John's iPhone\"", :yellow
192
- say ""
193
- say "💡 To get device IDs:", :cyan
192
+ say ''
193
+ say '💡 To get device IDs:', :cyan
194
194
  say " Run 'mysigner devices' to see all devices with their IDs", :cyan
195
195
  exit 1
196
196
  end
@@ -198,8 +198,8 @@ module Mysigner
198
198
  device_id = args[0]
199
199
  new_name = args[1]
200
200
 
201
- say "📱 Updating device...", :cyan
202
- say ""
201
+ say '📱 Updating device...', :cyan
202
+ say ''
203
203
 
204
204
  begin
205
205
  # Get device details first
@@ -208,7 +208,7 @@ module Mysigner
208
208
 
209
209
  say "Current name: #{device['name']}", :yellow
210
210
  say "New name: #{new_name}", :green
211
- say ""
211
+ say ''
212
212
 
213
213
  # Update device
214
214
  response = client.patch(
@@ -217,9 +217,9 @@ module Mysigner
217
217
  )
218
218
 
219
219
  updated_device = response[:data]['device']
220
- say "✓ Device updated successfully!", :green
221
- say ""
222
- say "Details:", :bold
220
+ say '✓ Device updated successfully!', :green
221
+ say ''
222
+ say 'Details:', :bold
223
223
  say " Name: #{updated_device['name']}"
224
224
  say " UDID: #{updated_device['udid']}"
225
225
  say " Platform: #{updated_device['platform']}"
@@ -234,7 +234,7 @@ module Mysigner
234
234
  invoke :help, ['device']
235
235
  else
236
236
  error "Unknown action: #{action}"
237
- say "Available actions: detect, add, update, help", :yellow
237
+ say 'Available actions: detect, add, update, help', :yellow
238
238
  exit 1
239
239
  end
240
240
  end
@@ -242,34 +242,32 @@ module Mysigner
242
242
  private
243
243
 
244
244
  def detect_connected_devices(config, client)
245
- say "🔍 Detecting connected iOS devices...", :cyan
246
- say ""
245
+ say '🔍 Detecting connected iOS devices...', :cyan
246
+ say ''
247
247
 
248
248
  devices = []
249
249
 
250
250
  # Try system_profiler first (built-in macOS)
251
- if system("which system_profiler > /dev/null 2>&1")
251
+ if system('which system_profiler > /dev/null 2>&1')
252
252
  output = `system_profiler SPUSBDataType 2>/dev/null`
253
-
253
+
254
254
  # Parse iOS devices from system_profiler output
255
255
  current_device = nil
256
256
  output.each_line do |line|
257
257
  if line =~ /^\s{4}(\S.+):$/
258
258
  # New top-level USB device
259
- current_device = { name: $1.strip }
259
+ current_device = { name: ::Regexp.last_match(1).strip }
260
260
  elsif current_device
261
261
  if line =~ /Serial Number:\s*([A-Fa-f0-9-]+)/
262
- serial = $1.gsub('-', '')
262
+ serial = ::Regexp.last_match(1).gsub('-', '')
263
263
  # iOS device serials are typically 24-40 hex chars
264
- if serial.length >= 24 && serial.length <= 40
265
- current_device[:udid] = serial
266
- end
264
+ current_device[:udid] = serial if serial.length.between?(24, 40)
267
265
  elsif line =~ /Product ID:\s*(0x12[aA][0-9a-fA-F])/
268
266
  # Apple mobile device product IDs start with 0x12a
269
267
  current_device[:is_ios] = true
270
268
  end
271
269
  end
272
-
270
+
273
271
  if current_device && current_device[:udid] && current_device[:is_ios]
274
272
  devices << current_device
275
273
  current_device = nil
@@ -278,76 +276,74 @@ module Mysigner
278
276
  end
279
277
 
280
278
  # Also try idevice_id if available (more reliable)
281
- if system("which idevice_id > /dev/null 2>&1")
279
+ if system('which idevice_id > /dev/null 2>&1')
282
280
  output = `idevice_id -l 2>/dev/null`.strip
283
281
  output.each_line do |line|
284
282
  udid = line.strip
285
283
  next if udid.empty?
286
-
284
+
287
285
  # Get device name if ideviceinfo is available
288
- name = "iOS Device"
289
- if system("which ideviceinfo > /dev/null 2>&1")
286
+ name = 'iOS Device'
287
+ if system('which ideviceinfo > /dev/null 2>&1')
290
288
  device_name = `ideviceinfo -u #{udid} -k DeviceName 2>/dev/null`.strip
291
289
  name = device_name unless device_name.empty?
292
290
  end
293
-
291
+
294
292
  # Avoid duplicates
295
- unless devices.any? { |d| d[:udid] == udid }
296
- devices << { name: name, udid: udid }
297
- end
293
+ devices << { name: name, udid: udid } unless devices.any? { |d| d[:udid] == udid }
298
294
  end
299
295
  end
300
296
 
301
297
  # Also try xcrun xctrace (Xcode command line tools)
302
- if devices.empty? && system("which xcrun > /dev/null 2>&1")
298
+ if devices.empty? && system('which xcrun > /dev/null 2>&1')
303
299
  output = `xcrun xctrace list devices 2>/dev/null`
304
300
  in_devices_section = false
305
-
301
+
306
302
  output.each_line do |line|
307
303
  line = line.strip
308
-
304
+
309
305
  # Track sections
310
- if line == "== Devices =="
306
+ if line == '== Devices =='
311
307
  in_devices_section = true
312
308
  next
313
- elsif line == "== Simulators =="
309
+ elsif line == '== Simulators =='
314
310
  in_devices_section = false
315
311
  next
316
312
  end
317
-
313
+
318
314
  next unless in_devices_section
319
315
  next if line.empty?
320
-
316
+
321
317
  # Format: "Name (Version) (UDID)" - iOS devices have UDIDs starting with 0000
322
318
  # Skip Macs which have UUID format
323
- if line =~ /^(.+?)\s+\([^)]+\)\s+\((0000[A-Fa-f0-9-]+)\)\s*$/
324
- name = $1.strip
325
- udid = $2.gsub('-', '')
326
- devices << { name: name, udid: udid }
327
- end
319
+ next unless line =~ /^(.+?)\s+\([^)]+\)\s+\((0000[A-Fa-f0-9-]+)\)\s*$/
320
+
321
+ name = ::Regexp.last_match(1).strip
322
+ udid = ::Regexp.last_match(2).gsub('-', '')
323
+ devices << { name: name, udid: udid }
328
324
  end
329
325
  end
330
326
 
331
327
  if devices.empty?
332
- say "No iOS devices detected.", :yellow
333
- say ""
334
- say "Make sure:", :cyan
335
- say " 1. Your device is connected via USB", :cyan
336
- say " 2. The device is unlocked", :cyan
328
+ say 'No iOS devices detected.', :yellow
329
+ say ''
330
+ say 'Make sure:', :cyan
331
+ say ' 1. Your device is connected via USB', :cyan
332
+ say ' 2. The device is unlocked', :cyan
337
333
  say " 3. You've trusted this computer on the device", :cyan
338
- say ""
339
- say "💡 For better detection, install libimobiledevice:", :yellow
340
- say " brew install libimobiledevice", :yellow
334
+ say ''
335
+ say '💡 For better detection, install libimobiledevice:', :yellow
336
+ say ' brew install libimobiledevice', :yellow
341
337
  return
342
338
  end
343
339
 
344
340
  say "Found #{devices.length} device(s):", :green
345
- say ""
341
+ say ''
346
342
 
347
343
  devices.each_with_index do |device, idx|
348
344
  say " #{idx + 1}. #{device[:name]}", :green
349
345
  say " UDID: #{device[:udid]}", :white
350
- say ""
346
+ say ''
351
347
  end
352
348
 
353
349
  # Check if running interactively
@@ -355,20 +351,20 @@ module Mysigner
355
351
 
356
352
  # Ask if user wants to register
357
353
  say "Would you like to register a device? (Enter number, or 'n' to skip)", :cyan
358
- choice = ask(">")&.strip || ''
354
+ choice = ask('>')&.strip || ''
359
355
 
360
356
  return if choice.downcase == 'n' || choice.empty?
361
357
 
362
358
  idx = choice.to_i - 1
363
359
  if idx >= 0 && idx < devices.length
364
360
  device = devices[idx]
365
-
366
- say ""
361
+
362
+ say ''
367
363
  say "Enter a name for this device (or press Enter to use '#{device[:name]}'):", :cyan
368
- custom_name = ask(">")&.strip || ''
364
+ custom_name = ask('>')&.strip || ''
369
365
  name = custom_name.empty? ? device[:name] : custom_name
370
366
 
371
- say ""
367
+ say ''
372
368
  say "📱 Registering '#{name}'...", :cyan
373
369
 
374
370
  begin
@@ -382,58 +378,59 @@ module Mysigner
382
378
  )
383
379
 
384
380
  registered = response[:data]['device']
385
- say ""
386
- say "✓ Device registered successfully!", :green
381
+ say ''
382
+ say '✓ Device registered successfully!', :green
387
383
  say " Name: #{registered['name']}"
388
384
  say " UDID: #{registered['udid']}"
389
385
  say " Platform: #{registered['platform']}"
390
386
  rescue Mysigner::ClientError => e
391
- if e.message.include?("already exists")
392
- say ""
393
- say "ℹ️ Device already registered", :yellow
387
+ if e.message.include?('already exists')
388
+ say ''
389
+ say 'ℹ️ Device already registered', :yellow
394
390
  else
395
391
  error "Failed to register: #{e.message}"
396
392
  end
397
393
  end
398
394
  else
399
- say "Invalid selection", :red
395
+ say 'Invalid selection', :red
400
396
  end
401
397
  end
402
398
 
403
399
  public
404
400
 
405
- desc "profiles", "List provisioning profiles (advanced - only needed for manual signing)"
401
+ desc 'profiles', 'List provisioning profiles (advanced - only needed for manual signing)'
406
402
  long_desc <<~DESC
407
403
  List all provisioning profiles in your organization.
408
-
404
+
409
405
  WHEN DO YOU NEED THIS?
410
-
406
+
411
407
  For Automatic Signing (Most Users):
412
408
  ❌ You DON'T need this - Xcode handles everything
413
-
409
+
414
410
  For Manual Signing (Advanced):
415
411
  ✅ View available profiles
416
412
  ✅ Check expiration dates
417
413
  ✅ Get profile IDs for download/delete
418
-
414
+
419
415
  EXAMPLES:
420
-
416
+
421
417
  # List all profiles
422
418
  mysigner profiles
423
-
419
+ #{' '}
424
420
  # Filter by type
425
421
  mysigner profiles --type APP_STORE
426
422
  mysigner profiles --type DEVELOPMENT
427
-
423
+ #{' '}
428
424
  # Filter by status
429
425
  mysigner profiles --status EXPIRED
430
-
426
+ #{' '}
431
427
  # Search by name
432
428
  mysigner profiles --search "MyApp"
433
-
429
+
434
430
  NOTE: Most users can skip this and just run 'mysigner build'
435
431
  DESC
436
- method_option :type, type: :string, aliases: '-t', desc: 'Filter by type (DEVELOPMENT, AD_HOC, APP_STORE, ENTERPRISE)'
432
+ method_option :type, type: :string, aliases: '-t',
433
+ desc: 'Filter by type (DEVELOPMENT, AD_HOC, APP_STORE, ENTERPRISE)'
437
434
  method_option :status, type: :string, aliases: '-s', desc: 'Filter by status (ACTIVE, EXPIRED, INVALID)'
438
435
  method_option :search, type: :string, aliases: '-q', desc: 'Search by name or identifier'
439
436
  method_option :page, type: :numeric, default: 1, desc: 'Page number'
@@ -442,8 +439,8 @@ module Mysigner
442
439
  config = load_config
443
440
  client = create_client(config)
444
441
 
445
- say "📄 Provisioning Profiles", :cyan
446
- say ""
442
+ say '📄 Provisioning Profiles', :cyan
443
+ say ''
447
444
 
448
445
  # Build query params
449
446
  params = {
@@ -460,9 +457,9 @@ module Mysigner
460
457
  pagination = response[:data]['pagination']
461
458
 
462
459
  if profiles.empty?
463
- say "No profiles found", :yellow
464
- say ""
465
- say "Tip: Profiles are created automatically when you request code signing", :yellow
460
+ say 'No profiles found', :yellow
461
+ say ''
462
+ say 'Tip: Profiles are created automatically when you request code signing', :yellow
466
463
  return
467
464
  end
468
465
 
@@ -475,22 +472,20 @@ module Mysigner
475
472
  say " ID: #{profile['id']} | Type: #{profile['profile_type'] || 'N/A'}"
476
473
  say " Bundle ID: #{profile['bundle_id_identifier'] || 'N/A'}"
477
474
  say " Status: #{profile['state'] || 'UNKNOWN'}"
478
-
475
+
479
476
  if profile['expires_at']
480
477
  expires = Time.parse(profile['expires_at']).strftime('%Y-%m-%d')
481
478
  say " Expires: #{expires}"
482
479
  end
483
-
484
- say ""
480
+
481
+ say ''
485
482
  end
486
483
 
487
484
  # Show pagination
488
485
  if pagination
489
486
  say "Page #{pagination['page']} of #{pagination['total_pages']} (#{pagination['total']} total)", :yellow
490
487
 
491
- if pagination['page'] < pagination['total_pages']
492
- say "Run with --page #{pagination['page'] + 1} to see more", :yellow
493
- end
488
+ say "Run with --page #{pagination['page'] + 1} to see more", :yellow if pagination['page'] < pagination['total_pages']
494
489
  end
495
490
  rescue Mysigner::ClientError => e
496
491
  error "Failed to fetch profiles: #{e.message}"
@@ -498,68 +493,68 @@ module Mysigner
498
493
  end
499
494
  end
500
495
 
501
- desc "profile SUBCOMMAND", "Download or delete profiles (advanced - only needed for manual signing)"
496
+ desc 'profile SUBCOMMAND', 'Download or delete profiles (advanced - only needed for manual signing)'
502
497
  long_desc <<~DESC
503
498
  Manage provisioning profiles for code signing.
504
-
499
+
505
500
  WHAT ARE PROVISIONING PROFILES?
506
-
501
+
507
502
  Provisioning profiles are required for signing iOS apps. They link:
508
503
  - Your signing certificate
509
504
  - Your App ID (bundle ID)
510
505
  - Authorized devices (for development/ad-hoc)
511
-
506
+
512
507
  WHEN DO YOU NEED THIS?
513
-
508
+
514
509
  For Automatic Signing (Most Users):
515
510
  ❌ You DON'T need these commands
516
511
  ✅ Xcode handles profiles automatically
517
512
  ✅ Just run: mysigner build
518
-
513
+
519
514
  For Manual Signing (Advanced):
520
515
  ✅ Download profiles from My Signer
521
516
  ✅ Install them to ~/Library/MobileDevice/Provisioning Profiles/
522
517
  ✅ Delete old/expired profiles
523
-
518
+
524
519
  SUBCOMMANDS:
525
-
520
+
526
521
  mysigner profile download ID [--output path]
527
522
  Download a provisioning profile
528
-
523
+ #{' '}
529
524
  mysigner profile delete ID
530
525
  Delete a provisioning profile
531
-
526
+
532
527
  HOW TO USE:
533
-
528
+
534
529
  1. List available profiles:
535
530
  mysigner profiles
536
-
531
+
537
532
  2. Download a profile:
538
533
  mysigner profile download 1
539
-
534
+
540
535
  3. Install it (double-click or manual):
541
536
  open Profile_Name.mobileprovision
542
537
  # Or: cp *.mobileprovision ~/Library/MobileDevice/Provisioning\\ Profiles/
543
-
538
+
544
539
  EXAMPLES:
545
-
540
+
546
541
  # Download profile ID 1
547
542
  mysigner profile download 1
548
-
543
+ #{' '}
549
544
  # Download to specific location
550
545
  mysigner profile download 1 --output ~/Desktop/MyProfile.mobileprovision
551
-
546
+ #{' '}
552
547
  # Delete expired profile
553
548
  mysigner profile delete 5
554
-
549
+ #{' '}
555
550
  # List all profiles
556
551
  mysigner profiles
557
-
552
+ #{' '}
558
553
  # Filter by type
559
554
  mysigner profiles --type APP_STORE
560
-
555
+
561
556
  NOTES:
562
-
557
+
563
558
  • Most users with Automatic signing don't need this
564
559
  • Manual signing wizard tries to auto-install profiles
565
560
  • Profiles expire after 1 year and must be regenerated
@@ -574,13 +569,13 @@ module Mysigner
574
569
  case action
575
570
  when 'download'
576
571
  if args.empty?
577
- error "Usage: mysigner profile download ID [--output path.mobileprovision]"
578
- say ""
579
- say "Example: mysigner profile download 1", :yellow
580
- say ""
581
- say "💡 To get profile IDs:", :cyan
572
+ error 'Usage: mysigner profile download ID [--output path.mobileprovision]'
573
+ say ''
574
+ say 'Example: mysigner profile download 1', :yellow
575
+ say ''
576
+ say '💡 To get profile IDs:', :cyan
582
577
  say " Run 'mysigner profiles' to see all profiles with their IDs", :cyan
583
- say ""
578
+ say ''
584
579
  say "Note: Most users with Automatic signing don't need this", :yellow
585
580
  say "Run 'mysigner help profile' for more info", :cyan
586
581
  exit 1
@@ -588,8 +583,8 @@ module Mysigner
588
583
 
589
584
  profile_id = args[0]
590
585
 
591
- say "📄 Downloading profile...", :cyan
592
- say ""
586
+ say '📄 Downloading profile...', :cyan
587
+ say ''
593
588
 
594
589
  begin
595
590
  # Get profile details first
@@ -598,38 +593,38 @@ module Mysigner
598
593
 
599
594
  # Determine output path
600
595
  output_path = if options[:output]
601
- options[:output]
602
- else
603
- # Use profile name, sanitize it for filename
604
- name = profile['name'] || "profile_#{profile['id']}"
605
- filename = name.gsub(/[^0-9A-Za-z.\-]/, '_')
606
- "#{filename}.mobileprovision"
607
- end
596
+ options[:output]
597
+ else
598
+ # Use profile name, sanitize it for filename
599
+ name = profile['name'] || "profile_#{profile['id']}"
600
+ filename = name.gsub(/[^0-9A-Za-z.-]/, '_')
601
+ "#{filename}.mobileprovision"
602
+ end
608
603
 
609
604
  # Download the profile content using the client's connection with auth
610
605
  download_url = "/api/v1/organizations/#{config.current_organization_id}/profiles/#{profile_id}/download"
611
-
612
- say "Fetching profile content...", :yellow
613
-
606
+
607
+ say 'Fetching profile content...', :yellow
608
+
614
609
  # Use Faraday directly with proper auth for binary download
615
610
  conn = Faraday.new(url: config.api_url) do |f|
616
611
  f.request :authorization, 'Bearer', config.api_token
617
612
  f.headers['X-User-Email'] = config.user_email if config.user_email
618
613
  f.adapter Faraday.default_adapter
619
614
  end
620
-
615
+
621
616
  response = conn.get(download_url) do |req|
622
- req.options.timeout = 30 # 30 second timeout
623
- req.options.open_timeout = 10 # 10 second connection timeout
617
+ req.options.timeout = 30 # 30 second timeout
618
+ req.options.open_timeout = 10 # 10 second connection timeout
624
619
  end
625
-
620
+
626
621
  unless response.success?
627
622
  # Check if it's a JSON error response
628
623
  if response.headers['content-type']&.include?('json')
629
624
  begin
630
625
  error_data = JSON.parse(response.body)
631
626
  error "Download failed: #{error_data['message'] || error_data['error']}"
632
- rescue
627
+ rescue StandardError
633
628
  error "Download failed with status #{response.status}"
634
629
  end
635
630
  else
@@ -641,62 +636,62 @@ module Mysigner
641
636
  # Write binary content directly to file
642
637
  File.binwrite(output_path, response.body)
643
638
 
644
- say "✓ Profile downloaded successfully!", :green
645
- say ""
646
- say "Details:", :bold
639
+ say '✓ Profile downloaded successfully!', :green
640
+ say ''
641
+ say 'Details:', :bold
647
642
  say " Name: #{profile['name']}"
648
643
  say " Type: #{profile['profile_type'] || 'N/A'}"
649
644
  say " Bundle ID: #{profile['bundle_id_identifier'] || 'N/A'}"
650
645
  say " Status: #{profile['state'] || 'UNKNOWN'}"
651
646
  say " File: #{output_path}"
652
- say ""
647
+ say ''
653
648
  say "File size: #{response.body.bytesize} bytes", :yellow
654
649
  rescue Mysigner::NotFoundError
655
650
  error "Profile not found with ID: #{profile_id}"
656
- say ""
657
- say "💡 Profile Not Found: How to fix", :cyan
658
- say ""
659
- say " → List available profiles: mysigner profiles", :yellow
660
- say " → Sync from Apple: mysigner sync ios", :yellow
661
- say " → Check ID is correct (IDs are numeric)", :yellow
662
- say ""
651
+ say ''
652
+ say '💡 Profile Not Found: How to fix', :cyan
653
+ say ''
654
+ say ' → List available profiles: mysigner profiles', :yellow
655
+ say ' → Sync from Apple: mysigner sync ios', :yellow
656
+ say ' → Check ID is correct (IDs are numeric)', :yellow
657
+ say ''
663
658
  exit 1
664
659
  rescue Mysigner::ClientError => e
665
660
  error "Failed to download profile: #{e.message}"
666
- say ""
667
- say "💡 Download Failed: Try these steps", :cyan
668
- say ""
669
- say " → Check your network connection", :yellow
670
- say " → Verify API token is valid: mysigner status", :yellow
671
- say " → Re-authenticate if needed: mysigner login", :yellow
672
- say ""
673
- exit 1
674
- rescue => e
661
+ say ''
662
+ say '💡 Download Failed: Try these steps', :cyan
663
+ say ''
664
+ say ' → Check your network connection', :yellow
665
+ say ' → Verify API token is valid: mysigner status', :yellow
666
+ say ' → Re-authenticate if needed: mysigner login', :yellow
667
+ say ''
668
+ exit 1
669
+ rescue StandardError => e
675
670
  error "Failed to save file: #{e.message}"
676
- say ""
677
- say "💡 File Save Failed: Check these", :cyan
678
- say ""
679
- say " → Verify you have write permissions to the directory", :yellow
680
- say " → Check disk space is available", :yellow
681
- say " → Try specifying a different output path with --output", :yellow
682
- say ""
671
+ say ''
672
+ say '💡 File Save Failed: Check these', :cyan
673
+ say ''
674
+ say ' → Verify you have write permissions to the directory', :yellow
675
+ say ' → Check disk space is available', :yellow
676
+ say ' → Try specifying a different output path with --output', :yellow
677
+ say ''
683
678
  exit 1
684
679
  end
685
680
  when 'delete'
686
681
  if args.empty?
687
- error "Usage: mysigner profile delete ID"
688
- say ""
689
- say "Example: mysigner profile delete 5", :yellow
690
- say ""
691
- say "💡 To get profile IDs:", :cyan
682
+ error 'Usage: mysigner profile delete ID'
683
+ say ''
684
+ say 'Example: mysigner profile delete 5', :yellow
685
+ say ''
686
+ say '💡 To get profile IDs:', :cyan
692
687
  say " Run 'mysigner profiles' to see all profiles with their IDs", :cyan
693
688
  exit 1
694
689
  end
695
690
 
696
691
  profile_id = args[0]
697
692
 
698
- say "📄 Deleting profile...", :cyan
699
- say ""
693
+ say '📄 Deleting profile...', :cyan
694
+ say ''
700
695
 
701
696
  begin
702
697
  # Get profile details first
@@ -704,18 +699,18 @@ module Mysigner
704
699
  profile = response[:data]
705
700
 
706
701
  # Confirm deletion
707
- say "You are about to delete:", :yellow
702
+ say 'You are about to delete:', :yellow
708
703
  say " Name: #{profile['name']}"
709
704
  say " Type: #{profile['profile_type']}"
710
705
  say " Bundle ID: #{profile['bundle_id_identifier'] || 'N/A'}"
711
- say ""
706
+ say ''
712
707
 
713
- if yes?("Are you sure you want to delete this profile? (y/n)")
708
+ if yes?('Are you sure you want to delete this profile? (y/n)')
714
709
  client.delete("/api/v1/organizations/#{config.current_organization_id}/profiles/#{profile_id}")
715
- say ""
716
- say "✓ Profile deleted successfully!", :green
710
+ say ''
711
+ say '✓ Profile deleted successfully!', :green
717
712
  else
718
- say "Deletion cancelled", :yellow
713
+ say 'Deletion cancelled', :yellow
719
714
  end
720
715
  rescue Mysigner::NotFoundError
721
716
  error "Profile not found with ID: #{profile_id}"
@@ -728,12 +723,12 @@ module Mysigner
728
723
  invoke :help, ['profile']
729
724
  else
730
725
  error "Unknown action: #{action}"
731
- say "Available actions: download, delete, help", :yellow
726
+ say 'Available actions: download, delete, help', :yellow
732
727
  exit 1
733
728
  end
734
729
  end
735
730
 
736
- desc "certificates", "List signing certificates from App Store Connect"
731
+ desc 'certificates', 'List signing certificates from App Store Connect'
737
732
  method_option :type, type: :string, aliases: '-p', desc: 'Filter by type (DEVELOPMENT, DISTRIBUTION)'
738
733
  method_option :status, type: :string, aliases: '-s', desc: 'Filter by status (ACTIVE, EXPIRED, REVOKED)'
739
734
  method_option :search, type: :string, aliases: '-q', desc: 'Search by name'
@@ -743,8 +738,8 @@ module Mysigner
743
738
  config = load_config
744
739
  client = create_client(config)
745
740
 
746
- say "🔐 Signing Certificates", :cyan
747
- say ""
741
+ say '🔐 Signing Certificates', :cyan
742
+ say ''
748
743
 
749
744
  # Build query params
750
745
  params = {
@@ -756,14 +751,15 @@ module Mysigner
756
751
  params[:q] = options[:search] if options[:search]
757
752
 
758
753
  begin
759
- response = client.get("/api/v1/organizations/#{config.current_organization_id}/certificates", params: params)
754
+ response = client.get("/api/v1/organizations/#{config.current_organization_id}/certificates",
755
+ params: params)
760
756
  certificates = response[:data]['certificates']
761
757
  pagination = response[:data]['pagination']
762
758
 
763
759
  if certificates.empty?
764
- say "No certificates found", :yellow
765
- say ""
766
- say "Tip: Certificates are synced automatically from App Store Connect", :yellow
760
+ say 'No certificates found', :yellow
761
+ say ''
762
+ say 'Tip: Certificates are synced automatically from App Store Connect', :yellow
767
763
  return
768
764
  end
769
765
 
@@ -771,27 +767,25 @@ module Mysigner
771
767
  certificates.each do |cert|
772
768
  status_icon = cert['status'] == 'ACTIVE' ? '✓' : '✗'
773
769
  status_color = cert['status'] == 'ACTIVE' ? :green : :red
774
-
770
+
775
771
  say " #{status_icon} #{cert['name']}", status_color
776
772
  say " ID: #{cert['id']} | Type: #{cert['certificate_type'] || 'N/A'}"
777
773
  say " Serial: #{cert['serial_number'] || 'N/A'}"
778
774
  say " Status: #{cert['status'] || 'UNKNOWN'}"
779
-
775
+
780
776
  if cert['expires_at']
781
777
  expires = Time.parse(cert['expires_at']).strftime('%Y-%m-%d')
782
778
  say " Expires: #{expires}"
783
779
  end
784
-
785
- say ""
780
+
781
+ say ''
786
782
  end
787
783
 
788
784
  # Show pagination
789
785
  if pagination
790
786
  say "Page #{pagination['page']} of #{pagination['total_pages']} (#{pagination['total']} total)", :yellow
791
787
 
792
- if pagination['page'] < pagination['total_pages']
793
- say "Run with --page #{pagination['page'] + 1} to see more", :yellow
794
- end
788
+ say "Run with --page #{pagination['page'] + 1} to see more", :yellow if pagination['page'] < pagination['total_pages']
795
789
  end
796
790
  rescue Mysigner::ClientError => e
797
791
  error "Failed to fetch certificates: #{e.message}"
@@ -799,12 +793,12 @@ module Mysigner
799
793
  end
800
794
  end
801
795
 
802
- desc "certificate ACTION", "Check local keychain or download certificates (check, download)"
796
+ desc 'certificate ACTION', 'Check local keychain or download certificates (check, download)'
803
797
  long_desc <<~DESC
804
798
  Actions:
805
799
  check - Check certificates installed in your Mac's Keychain (not API)
806
800
  download ID - Download a certificate from My Signer API
807
-
801
+
808
802
  Note: 'check' scans your LOCAL Keychain, not certificates in My Signer API.
809
803
  Use 'mysigner certificates' to see API certificates.
810
804
  DESC
@@ -816,111 +810,114 @@ module Mysigner
816
810
  case action
817
811
  when 'check'
818
812
  require_relative '../signing/certificate_checker'
819
-
820
- say "🔍 Checking local certificates...", :cyan
821
- say ""
822
-
813
+
814
+ say '🔍 Checking local certificates...', :cyan
815
+ say ''
816
+
823
817
  checker = Signing::CertificateChecker.new
824
-
818
+
825
819
  begin
826
820
  certificates = checker.check!
827
-
821
+
828
822
  if certificates.empty?
829
- say "No code signing certificates found in local Keychain", :yellow
830
- say ""
831
- say "⚠️ Important:", :yellow
832
- say " This command checks certificates INSTALLED ON YOUR MAC.", :white
833
- say " Certificates in My Signer API are not automatically installed locally.", :white
834
- say ""
835
- say "To install certificates:", :cyan
836
- say " 1. List certificates in My Signer: mysigner certificates", :white
837
- say " 2. Download one: mysigner certificate download <ID>", :white
838
- say " 3. Double-click the .cer file to install in Keychain", :white
839
- say ""
840
- say "Or download from Apple Developer:", :cyan
841
- say " https://developer.apple.com/account/resources/certificates/list", :white
823
+ say 'No code signing certificates found in local Keychain', :yellow
824
+ say ''
825
+ say '⚠️ Important:', :yellow
826
+ say ' This command checks certificates INSTALLED ON YOUR MAC.', :white
827
+ say ' Certificates in My Signer API are not automatically installed locally.', :white
828
+ say ''
829
+ say 'To install certificates:', :cyan
830
+ say ' 1. List certificates in My Signer: mysigner certificates', :white
831
+ say ' 2. Download one: mysigner certificate download <ID>', :white
832
+ say ' 3. Double-click the .cer file to install in Keychain', :white
833
+ say ''
834
+ say 'Or download from Apple Developer:', :cyan
835
+ say ' https://developer.apple.com/account/resources/certificates/list', :white
842
836
  return
843
837
  end
844
-
838
+
845
839
  # Group by status
846
840
  by_status = checker.by_status
847
-
841
+
848
842
  # Show valid certificates
849
843
  if by_status[:valid].any?
850
844
  say "✓ Valid Certificates (#{by_status[:valid].count})", :green
851
- say ""
845
+ say ''
852
846
  by_status[:valid].each do |cert|
853
847
  say " #{cert[:name]}", :green
854
848
  say " Type: #{cert[:type]}"
855
849
  say " Team: #{cert[:team_id] || 'Unknown'}"
856
- say " Expires: #{cert[:expires_at].strftime('%Y-%m-%d')} (#{cert[:days_until_expiry]} days)", :white
857
- say ""
850
+ say " Expires: #{cert[:expires_at].strftime('%Y-%m-%d')} (#{cert[:days_until_expiry]} days)",
851
+ :white
852
+ say ''
858
853
  end
859
854
  end
860
-
855
+
861
856
  # Show expiring soon certificates
862
857
  if by_status[:expiring_soon].any?
863
858
  say "⚠️ Expiring Soon (#{by_status[:expiring_soon].count})", :yellow
864
- say ""
859
+ say ''
865
860
  by_status[:expiring_soon].each do |cert|
866
861
  say " #{cert[:name]}", :yellow
867
862
  say " Type: #{cert[:type]}"
868
863
  say " Team: #{cert[:team_id] || 'Unknown'}"
869
- say " Expires: #{cert[:expires_at].strftime('%Y-%m-%d')} (#{cert[:days_until_expiry]} days)", :yellow
870
- say ""
864
+ say " Expires: #{cert[:expires_at].strftime('%Y-%m-%d')} (#{cert[:days_until_expiry]} days)",
865
+ :yellow
866
+ say ''
871
867
  end
872
- say "Renew these certificates soon to avoid build failures!", :yellow
873
- say ""
868
+ say 'Renew these certificates soon to avoid build failures!', :yellow
869
+ say ''
874
870
  end
875
-
871
+
876
872
  # Show expired certificates
877
873
  if by_status[:expired].any?
878
874
  say "✗ Expired Certificates (#{by_status[:expired].count})", :red
879
- say ""
875
+ say ''
880
876
  by_status[:expired].each do |cert|
881
877
  say " #{cert[:name]}", :red
882
878
  say " Type: #{cert[:type]}"
883
879
  say " Team: #{cert[:team_id] || 'Unknown'}"
884
- say " Expired: #{cert[:expires_at].strftime('%Y-%m-%d')} (#{cert[:days_until_expiry].abs} days ago)", :red
885
- say ""
880
+ say " Expired: #{cert[:expires_at].strftime('%Y-%m-%d')} (#{cert[:days_until_expiry].abs} days ago)",
881
+ :red
882
+ say ''
886
883
  end
887
- say "These certificates will cause build failures. Renew them at:", :red
888
- say " https://developer.apple.com/account/resources/certificates/list", :white
889
- say ""
884
+ say 'These certificates will cause build failures. Renew them at:', :red
885
+ say ' https://developer.apple.com/account/resources/certificates/list', :white
886
+ say ''
890
887
  end
891
-
888
+
892
889
  # Summary
893
- say "" * 80, :cyan
894
- say "Total: #{certificates.count} certificate#{certificates.count == 1 ? '' : 's'} installed locally", :cyan
890
+ say '' * 80, :cyan
891
+ say "Total: #{certificates.count} certificate#{'s' unless certificates.one?} installed locally",
892
+ :cyan
895
893
  if checker.has_issues?
896
- say "Status: ⚠️ Action required", :yellow
894
+ say 'Status: ⚠️ Action required', :yellow
897
895
  else
898
- say "Status: ✓ All certificates valid", :green
896
+ say 'Status: ✓ All certificates valid', :green
899
897
  end
900
- say ""
901
- say "💡 Tip: These are certificates INSTALLED ON YOUR MAC.", :cyan
902
- say " To see all certificates in My Signer API, run: mysigner certificates", :white
903
-
898
+ say ''
899
+ say '💡 Tip: These are certificates INSTALLED ON YOUR MAC.', :cyan
900
+ say ' To see all certificates in My Signer API, run: mysigner certificates', :white
904
901
  rescue Signing::CertificateChecker::CheckError => e
905
902
  error "Certificate check failed: #{e.message}"
906
- say ""
907
- say "This usually means:", :yellow
908
- say " • Keychain is locked", :white
909
- say " • No certificates installed", :white
910
- say " • Security command not available", :white
903
+ say ''
904
+ say 'This usually means:', :yellow
905
+ say ' • Keychain is locked', :white
906
+ say ' • No certificates installed', :white
907
+ say ' • Security command not available', :white
911
908
  exit 1
912
909
  end
913
-
910
+
914
911
  when 'download'
915
912
  if args.empty?
916
- error "Usage: mysigner certificate download ID [--output path.cer]"
913
+ error 'Usage: mysigner certificate download ID [--output path.cer]'
917
914
  exit 1
918
915
  end
919
916
 
920
917
  certificate_id = args[0]
921
918
 
922
- say "🔐 Downloading certificate...", :cyan
923
- say ""
919
+ say '🔐 Downloading certificate...', :cyan
920
+ say ''
924
921
 
925
922
  begin
926
923
  # Get certificate details first
@@ -929,38 +926,38 @@ module Mysigner
929
926
 
930
927
  # Determine output path
931
928
  output_path = if options[:output]
932
- options[:output]
933
- else
934
- # Use certificate name, sanitize it for filename
935
- name = certificate['name'] || "certificate_#{certificate['id']}"
936
- filename = name.gsub(/[^0-9A-Za-z.\-]/, '_')
937
- "#{filename}.cer"
938
- end
929
+ options[:output]
930
+ else
931
+ # Use certificate name, sanitize it for filename
932
+ name = certificate['name'] || "certificate_#{certificate['id']}"
933
+ filename = name.gsub(/[^0-9A-Za-z.-]/, '_')
934
+ "#{filename}.cer"
935
+ end
939
936
 
940
937
  # Download the certificate content (binary response)
941
938
  download_url = "/api/v1/organizations/#{config.current_organization_id}/certificates/#{certificate_id}/download"
942
-
943
- say "Fetching certificate content...", :yellow
944
-
939
+
940
+ say 'Fetching certificate content...', :yellow
941
+
945
942
  # Use Faraday directly with proper auth for binary download
946
943
  conn = Faraday.new(url: config.api_url) do |f|
947
944
  f.request :authorization, 'Bearer', config.api_token
948
945
  f.headers['X-User-Email'] = config.user_email if config.user_email
949
946
  f.adapter Faraday.default_adapter
950
947
  end
951
-
948
+
952
949
  response = conn.get(download_url) do |req|
953
- req.options.timeout = 30 # 30 second timeout
954
- req.options.open_timeout = 10 # 10 second connection timeout
950
+ req.options.timeout = 30 # 30 second timeout
951
+ req.options.open_timeout = 10 # 10 second connection timeout
955
952
  end
956
-
953
+
957
954
  unless response.success?
958
955
  # Check if it's a JSON error response
959
956
  if response.headers['content-type']&.include?('json')
960
957
  begin
961
958
  error_data = JSON.parse(response.body)
962
959
  error "Download failed: #{error_data['message'] || error_data['error']}"
963
- rescue
960
+ rescue StandardError
964
961
  error "Download failed with status #{response.status}"
965
962
  end
966
963
  else
@@ -972,59 +969,59 @@ module Mysigner
972
969
  # Write binary content directly to file
973
970
  File.binwrite(output_path, response.body)
974
971
 
975
- say "✓ Certificate downloaded successfully!", :green
976
- say ""
977
- say "Details:", :bold
972
+ say '✓ Certificate downloaded successfully!', :green
973
+ say ''
974
+ say 'Details:', :bold
978
975
  say " Name: #{certificate['name']}"
979
976
  say " Type: #{certificate['certificate_type'] || 'N/A'}"
980
977
  say " Serial: #{certificate['serial_number'] || 'N/A'}"
981
978
  say " Status: #{certificate['status'] || 'UNKNOWN'}"
982
979
  say " File: #{output_path}"
983
- say ""
980
+ say ''
984
981
  say "File size: #{response.body.bytesize} bytes", :yellow
985
982
  rescue Mysigner::NotFoundError
986
983
  error "Certificate not found with ID: #{certificate_id}"
987
- say ""
988
- say "💡 Certificate Not Found: How to fix", :cyan
989
- say ""
990
- say " → List available certificates: mysigner certificates", :yellow
991
- say " → Sync from Apple: mysigner sync ios", :yellow
992
- say " → Check the ID is correct (IDs are numeric)", :yellow
993
- say ""
984
+ say ''
985
+ say '💡 Certificate Not Found: How to fix', :cyan
986
+ say ''
987
+ say ' → List available certificates: mysigner certificates', :yellow
988
+ say ' → Sync from Apple: mysigner sync ios', :yellow
989
+ say ' → Check the ID is correct (IDs are numeric)', :yellow
990
+ say ''
994
991
  exit 1
995
992
  rescue Mysigner::ClientError => e
996
993
  error "Failed to download certificate: #{e.message}"
997
- say ""
998
- say "💡 Download Failed: Try these steps", :cyan
999
- say ""
1000
- say " → Check your network connection", :yellow
1001
- say " → Verify API token is valid: mysigner status", :yellow
1002
- say " → Re-authenticate if needed: mysigner login", :yellow
1003
- say ""
1004
- exit 1
1005
- rescue => e
994
+ say ''
995
+ say '💡 Download Failed: Try these steps', :cyan
996
+ say ''
997
+ say ' → Check your network connection', :yellow
998
+ say ' → Verify API token is valid: mysigner status', :yellow
999
+ say ' → Re-authenticate if needed: mysigner login', :yellow
1000
+ say ''
1001
+ exit 1
1002
+ rescue StandardError => e
1006
1003
  error "Failed to save file: #{e.message}"
1007
- say ""
1008
- say "💡 File Save Failed: Check these", :cyan
1009
- say ""
1010
- say " → Verify you have write permissions to the directory", :yellow
1011
- say " → Check disk space is available", :yellow
1012
- say " → Try specifying a different output path with --output", :yellow
1013
- say ""
1004
+ say ''
1005
+ say '💡 File Save Failed: Check these', :cyan
1006
+ say ''
1007
+ say ' → Verify you have write permissions to the directory', :yellow
1008
+ say ' → Check disk space is available', :yellow
1009
+ say ' → Try specifying a different output path with --output', :yellow
1010
+ say ''
1014
1011
  exit 1
1015
1012
  end
1016
1013
  when 'help'
1017
1014
  invoke :help, ['certificate']
1018
1015
  else
1019
1016
  error "Unknown action: #{action}"
1020
- say "Available actions: check, download, help", :yellow
1017
+ say 'Available actions: check, download, help', :yellow
1021
1018
  exit 1
1022
1019
  end
1023
1020
  end
1024
1021
 
1025
1022
  # ==================== ANDROID KEYSTORES ====================
1026
1023
 
1027
- desc "keystore SUBCOMMAND", "Manage Android keystores (list, upload, download, delete, activate)"
1024
+ desc 'keystore SUBCOMMAND', 'Manage Android keystores (list, upload, download, delete, activate)'
1028
1025
  long_desc <<~DESC
1029
1026
  Manage Android keystores for signing your apps.
1030
1027
 
@@ -1087,38 +1084,38 @@ module Mysigner
1087
1084
 
1088
1085
  case action
1089
1086
  when 'list'
1090
- say "🔐 Android Keystores", :cyan
1091
- say ""
1087
+ say '🔐 Android Keystores', :cyan
1088
+ say ''
1092
1089
 
1093
1090
  keystores = manager.list(android_app_id: options[:app_id])
1094
1091
 
1095
1092
  if keystores.empty?
1096
- say "No keystores found", :yellow
1097
- say ""
1098
- say "Upload a keystore with: mysigner keystore upload PATH", :yellow
1093
+ say 'No keystores found', :yellow
1094
+ say ''
1095
+ say 'Upload a keystore with: mysigner keystore upload PATH', :yellow
1099
1096
  return
1100
1097
  end
1101
1098
 
1102
1099
  keystores.each do |ks|
1103
1100
  active_icon = ks['active'] ? '✓' : '○'
1104
1101
  active_color = ks['active'] ? :green : :white
1105
-
1102
+
1106
1103
  say " #{active_icon} #{ks['name']} (ID: #{ks['id']})", active_color
1107
1104
  say " Key Alias: #{ks['key_alias'] || 'N/A'}"
1108
1105
  say " App: #{ks['package_name']}" if ks['package_name']
1109
1106
  say " Active: #{ks['active'] ? 'Yes' : 'No'}"
1110
- say ""
1107
+ say ''
1111
1108
  end
1112
1109
 
1113
1110
  say "Total: #{keystores.count} keystore(s)", :yellow
1114
1111
 
1115
1112
  when 'upload'
1116
1113
  keystore_path = args[0]
1117
-
1114
+
1118
1115
  unless keystore_path
1119
- error "Usage: mysigner keystore upload PATH"
1120
- say ""
1121
- say "Example: mysigner keystore upload ~/keys/release.jks", :yellow
1116
+ error 'Usage: mysigner keystore upload PATH'
1117
+ say ''
1118
+ say 'Example: mysigner keystore upload ~/keys/release.jks', :yellow
1122
1119
  exit 1
1123
1120
  end
1124
1121
 
@@ -1127,16 +1124,16 @@ module Mysigner
1127
1124
  exit 1
1128
1125
  end
1129
1126
 
1130
- say "🔐 Uploading keystore...", :cyan
1131
- say ""
1127
+ say '🔐 Uploading keystore...', :cyan
1128
+ say ''
1132
1129
 
1133
1130
  # Get keystore details
1134
1131
  name = options[:name] || ask("Keystore name (e.g., 'Release Key'):")
1135
- key_alias = options[:alias] || ask("Key alias:")
1136
- password = ask("Keystore password:", echo: false)
1137
- say ""
1138
- key_password = ask("Key password (press Enter if same as keystore):", echo: false)
1139
- say ""
1132
+ key_alias = options[:alias] || ask('Key alias:')
1133
+ password = ask('Keystore password:', echo: false)
1134
+ say ''
1135
+ key_password = ask('Key password (press Enter if same as keystore):', echo: false)
1136
+ say ''
1140
1137
  key_password = password if key_password.empty?
1141
1138
 
1142
1139
  begin
@@ -1150,52 +1147,51 @@ module Mysigner
1150
1147
  active: true
1151
1148
  )
1152
1149
 
1153
- say "✓ Keystore uploaded successfully!", :green
1154
- say ""
1155
- say "Details:", :bold
1150
+ say '✓ Keystore uploaded successfully!', :green
1151
+ say ''
1152
+ say 'Details:', :bold
1156
1153
  say " ID: #{result['id']}"
1157
1154
  say " Name: #{result['name']}"
1158
1155
  say " Key Alias: #{result['key_alias']}"
1159
1156
  say " Active: #{result['active']}"
1160
- say ""
1161
-
1157
+ say ''
1162
1158
  rescue Signing::KeystoreManager::KeystoreError => e
1163
1159
  error "Upload failed: #{e.message}"
1164
- say ""
1165
- say "💡 Keystore Upload Failed: Common issues", :cyan
1166
- say ""
1167
- say " → Verify the keystore file is valid (.jks or .keystore)", :yellow
1168
- say " → Check keystore password is correct", :yellow
1169
- say " → Check key alias exists in the keystore", :yellow
1170
- say " → Verify key password is correct", :yellow
1171
- say ""
1160
+ say ''
1161
+ say '💡 Keystore Upload Failed: Common issues', :cyan
1162
+ say ''
1163
+ say ' → Verify the keystore file is valid (.jks or .keystore)', :yellow
1164
+ say ' → Check keystore password is correct', :yellow
1165
+ say ' → Check key alias exists in the keystore', :yellow
1166
+ say ' → Verify key password is correct', :yellow
1167
+ say ''
1172
1168
  say " Test with: keytool -list -keystore #{keystore_path}", :green
1173
- say ""
1169
+ say ''
1174
1170
  exit 1
1175
1171
  rescue Mysigner::ClientError => e
1176
1172
  error "API error: #{e.message}"
1177
- say ""
1178
- say "💡 API Error: Try these steps", :cyan
1179
- say ""
1180
- say " → Check your network connection", :yellow
1181
- say " → Verify API token is valid: mysigner status", :yellow
1182
- say " → Re-authenticate if needed: mysigner login", :yellow
1183
- say ""
1173
+ say ''
1174
+ say '💡 API Error: Try these steps', :cyan
1175
+ say ''
1176
+ say ' → Check your network connection', :yellow
1177
+ say ' → Verify API token is valid: mysigner status', :yellow
1178
+ say ' → Re-authenticate if needed: mysigner login', :yellow
1179
+ say ''
1184
1180
  exit 1
1185
1181
  end
1186
1182
 
1187
1183
  when 'download'
1188
1184
  keystore_id = args[0]
1189
-
1185
+
1190
1186
  unless keystore_id
1191
- error "Usage: mysigner keystore download ID"
1192
- say ""
1187
+ error 'Usage: mysigner keystore download ID'
1188
+ say ''
1193
1189
  say "Run 'mysigner keystore list' to see available IDs", :yellow
1194
1190
  exit 1
1195
1191
  end
1196
1192
 
1197
- say "🔐 Downloading keystore...", :cyan
1198
- say ""
1193
+ say '🔐 Downloading keystore...', :cyan
1194
+ say ''
1199
1195
 
1200
1196
  begin
1201
1197
  result = manager.download(keystore_id)
@@ -1206,110 +1202,109 @@ module Mysigner
1206
1202
  result[:path] = options[:output]
1207
1203
  end
1208
1204
 
1209
- say "✓ Keystore downloaded!", :green
1210
- say ""
1211
- say "Details:", :bold
1205
+ say '✓ Keystore downloaded!', :green
1206
+ say ''
1207
+ say 'Details:', :bold
1212
1208
  say " Name: #{result[:name]}"
1213
1209
  say " Key Alias: #{result[:key_alias]}"
1214
1210
  say " Path: #{result[:path]}"
1215
- say ""
1216
- say "⚠️ Keep this file secure and backed up!", :yellow
1217
-
1211
+ say ''
1212
+ say '⚠️ Keep this file secure and backed up!', :yellow
1218
1213
  rescue Signing::KeystoreManager::KeystoreNotFoundError => e
1219
1214
  error "Keystore not found: #{e.message}"
1220
- say ""
1221
- say "💡 Keystore Not Found: How to fix", :cyan
1222
- say ""
1223
- say " → List available keystores: mysigner keystore list", :yellow
1224
- say " → Upload a keystore: mysigner keystore upload <path>", :yellow
1225
- say " → Check the ID is correct (IDs are numeric)", :yellow
1226
- say ""
1215
+ say ''
1216
+ say '💡 Keystore Not Found: How to fix', :cyan
1217
+ say ''
1218
+ say ' → List available keystores: mysigner keystore list', :yellow
1219
+ say ' → Upload a keystore: mysigner keystore upload <path>', :yellow
1220
+ say ' → Check the ID is correct (IDs are numeric)', :yellow
1221
+ say ''
1227
1222
  exit 1
1228
1223
  rescue Signing::KeystoreManager::DownloadError => e
1229
1224
  error "Download failed: #{e.message}"
1230
- say ""
1231
- say "💡 Download Failed: Try these steps", :cyan
1232
- say ""
1233
- say " → Check your network connection", :yellow
1234
- say " → Verify API token is valid: mysigner status", :yellow
1235
- say " → Re-authenticate if needed: mysigner login", :yellow
1236
- say ""
1225
+ say ''
1226
+ say '💡 Download Failed: Try these steps', :cyan
1227
+ say ''
1228
+ say ' → Check your network connection', :yellow
1229
+ say ' → Verify API token is valid: mysigner status', :yellow
1230
+ say ' → Re-authenticate if needed: mysigner login', :yellow
1231
+ say ''
1237
1232
  exit 1
1238
1233
  end
1239
1234
 
1240
1235
  when 'delete'
1241
1236
  keystore_id = args[0]
1242
-
1237
+
1243
1238
  unless keystore_id
1244
- error "Usage: mysigner keystore delete ID"
1239
+ error 'Usage: mysigner keystore delete ID'
1245
1240
  exit 1
1246
1241
  end
1247
1242
 
1248
1243
  # Get keystore details first
1249
1244
  keystores = manager.list
1250
1245
  keystore = keystores.find { |k| k['id'].to_s == keystore_id.to_s }
1251
-
1246
+
1252
1247
  unless keystore
1253
1248
  error "Keystore not found with ID: #{keystore_id}"
1254
- say ""
1255
- say "💡 Keystore Not Found: How to fix", :cyan
1256
- say ""
1257
- say " → List available keystores: mysigner keystore list", :yellow
1258
- say " → Upload a keystore: mysigner keystore upload <path>", :yellow
1259
- say ""
1249
+ say ''
1250
+ say '💡 Keystore Not Found: How to fix', :cyan
1251
+ say ''
1252
+ say ' → List available keystores: mysigner keystore list', :yellow
1253
+ say ' → Upload a keystore: mysigner keystore upload <path>', :yellow
1254
+ say ''
1260
1255
  exit 1
1261
1256
  end
1262
1257
 
1263
- say "⚠️ You are about to delete:", :yellow
1258
+ say '⚠️ You are about to delete:', :yellow
1264
1259
  say " Name: #{keystore['name']}"
1265
1260
  say " Key Alias: #{keystore['key_alias']}"
1266
- say ""
1261
+ say ''
1267
1262
 
1268
- if yes?("Are you sure? This cannot be undone. (y/n)")
1263
+ if yes?('Are you sure? This cannot be undone. (y/n)')
1269
1264
  begin
1270
1265
  manager.delete(keystore_id)
1271
- say ""
1272
- say "✓ Keystore deleted", :green
1266
+ say ''
1267
+ say '✓ Keystore deleted', :green
1273
1268
  rescue Mysigner::ClientError => e
1274
1269
  error "Delete failed: #{e.message}"
1275
1270
  exit 1
1276
1271
  end
1277
1272
  else
1278
- say "Deletion cancelled", :yellow
1273
+ say 'Deletion cancelled', :yellow
1279
1274
  end
1280
1275
 
1281
1276
  when 'activate'
1282
1277
  keystore_id = args[0]
1283
-
1278
+
1284
1279
  unless keystore_id
1285
- error "Usage: mysigner keystore activate ID"
1280
+ error 'Usage: mysigner keystore activate ID'
1286
1281
  exit 1
1287
1282
  end
1288
1283
 
1289
- say "🔐 Activating keystore...", :cyan
1284
+ say '🔐 Activating keystore...', :cyan
1290
1285
 
1291
1286
  begin
1292
1287
  result = manager.activate(keystore_id)
1293
- say "✓ Keystore activated!", :green
1294
- say ""
1288
+ say '✓ Keystore activated!', :green
1289
+ say ''
1295
1290
  say "#{result['name']} is now the default keystore", :cyan
1296
1291
  rescue Mysigner::NotFoundError
1297
1292
  error "Keystore not found with ID: #{keystore_id}"
1298
- say ""
1299
- say "💡 Keystore Not Found: How to fix", :cyan
1300
- say ""
1301
- say " → List available keystores: mysigner keystore list", :yellow
1302
- say " → Upload a keystore: mysigner keystore upload <path>", :yellow
1303
- say ""
1293
+ say ''
1294
+ say '💡 Keystore Not Found: How to fix', :cyan
1295
+ say ''
1296
+ say ' → List available keystores: mysigner keystore list', :yellow
1297
+ say ' → Upload a keystore: mysigner keystore upload <path>', :yellow
1298
+ say ''
1304
1299
  exit 1
1305
1300
  rescue Mysigner::ClientError => e
1306
1301
  error "Activation failed: #{e.message}"
1307
- say ""
1308
- say "💡 Activation Failed: Try these steps", :cyan
1309
- say ""
1310
- say " → Verify keystore ID is correct: mysigner keystore list", :yellow
1311
- say " → Check API token is valid: mysigner status", :yellow
1312
- say ""
1302
+ say ''
1303
+ say '💡 Activation Failed: Try these steps', :cyan
1304
+ say ''
1305
+ say ' → Verify keystore ID is correct: mysigner keystore list', :yellow
1306
+ say ' → Check API token is valid: mysigner status', :yellow
1307
+ say ''
1313
1308
  exit 1
1314
1309
  end
1315
1310
 
@@ -1317,14 +1312,14 @@ module Mysigner
1317
1312
  invoke :help, ['keystore']
1318
1313
  else
1319
1314
  error "Unknown action: #{action}"
1320
- say "Available actions: list, upload, download, delete, activate, help", :yellow
1315
+ say 'Available actions: list, upload, download, delete, activate, help', :yellow
1321
1316
  exit 1
1322
1317
  end
1323
1318
  end
1324
1319
 
1325
1320
  # ==================== ANDROID APP REGISTRATION ====================
1326
1321
 
1327
- desc "android SUBCOMMAND", "Android commands (init, add, build, list)"
1322
+ desc 'android SUBCOMMAND', 'Android commands (init, add, build, list)'
1328
1323
  long_desc <<~DESC
1329
1324
  Register and manage Android apps with My Signer.
1330
1325
 
@@ -1375,9 +1370,9 @@ module Mysigner
1375
1370
  android_init(config, client)
1376
1371
  when 'add'
1377
1372
  if args.empty?
1378
- error "Usage: mysigner android add PACKAGE_NAME [--name NAME]"
1379
- say ""
1380
- say "Example: mysigner android add com.example.myapp --name \"My App\"", :yellow
1373
+ error 'Usage: mysigner android add PACKAGE_NAME [--name NAME]'
1374
+ say ''
1375
+ say 'Example: mysigner android add com.example.myapp --name "My App"', :yellow
1381
1376
  exit 1
1382
1377
  end
1383
1378
  android_add(config, client, args[0], options[:name])
@@ -1390,7 +1385,7 @@ module Mysigner
1390
1385
  invoke :help, ['android']
1391
1386
  else
1392
1387
  error "Unknown action: #{action}"
1393
- say "Available actions: init, add, build, list, help", :yellow
1388
+ say 'Available actions: init, add, build, list, help', :yellow
1394
1389
  exit 1
1395
1390
  end
1396
1391
  end
@@ -1400,8 +1395,8 @@ module Mysigner
1400
1395
  def android_init(config, client)
1401
1396
  require_relative '../build/android_parser'
1402
1397
 
1403
- say "🔍 Detecting project...", :cyan
1404
- say ""
1398
+ say '🔍 Detecting project...', :cyan
1399
+ say ''
1405
1400
 
1406
1401
  package_name = nil
1407
1402
  app_name = nil
@@ -1422,26 +1417,26 @@ module Mysigner
1422
1417
  app_name = expo_config[:name]
1423
1418
  project_type = 'Expo managed'
1424
1419
  else
1425
- error "No Android project or Expo config found"
1426
- say ""
1420
+ error 'No Android project or Expo config found'
1421
+ say ''
1427
1422
  say "For Expo managed projects, add 'android.package' to app.json:", :yellow
1428
- say ""
1429
- say " {", :white
1430
- say " \"expo\": {", :white
1431
- say " \"android\": { \"package\": \"com.yourcompany.app\" }", :white
1432
- say " }", :white
1433
- say " }", :white
1434
- say ""
1435
- say "Or run from a directory with an android/ folder (native/RN/Capacitor).", :yellow
1423
+ say ''
1424
+ say ' {', :white
1425
+ say ' "expo": {', :white
1426
+ say ' "android": { "package": "com.yourcompany.app" }', :white
1427
+ say ' }', :white
1428
+ say ' }', :white
1429
+ say ''
1430
+ say 'Or run from a directory with an android/ folder (native/RN/Capacitor).', :yellow
1436
1431
  exit 1
1437
1432
  end
1438
1433
  end
1439
1434
 
1440
1435
  say "✓ Found: #{project_type} project", :green
1441
- say ""
1436
+ say ''
1442
1437
  say "📦 Package: #{package_name}", :cyan
1443
1438
  say "📱 Name: #{app_name || '(not set)'}", :cyan
1444
- say ""
1439
+ say ''
1445
1440
 
1446
1441
  # Check if app already exists
1447
1442
  begin
@@ -1453,21 +1448,21 @@ module Mysigner
1453
1448
  existing = existing_apps.find { |a| a['package_name'] == package_name }
1454
1449
 
1455
1450
  if existing
1456
- say "ℹ️ App already registered!", :yellow
1457
- say ""
1458
- say "Details:", :bold
1451
+ say 'ℹ️ App already registered!', :yellow
1452
+ say ''
1453
+ say 'Details:', :bold
1459
1454
  say " ID: #{existing['id']}"
1460
1455
  say " Name: #{existing['name'] || '(not set)'}"
1461
1456
  say " Package: #{existing['package_name']}"
1462
1457
  say " Builds: #{existing['builds_count'] || 0}"
1463
- say ""
1464
- say "Next steps:", :cyan
1465
- if (existing['builds_count'] || 0) > 0
1466
- say " • Ship to Play Store: mysigner ship internal --platform android", :white
1458
+ say ''
1459
+ say 'Next steps:', :cyan
1460
+ if (existing['builds_count'] || 0).positive?
1461
+ say ' • Ship to Play Store: mysigner ship internal --platform android', :white
1467
1462
  else
1468
- say " 1. Build AAB: mysigner android build", :white
1469
- say " 2. Upload first build manually in Play Console (one-time requirement)", :white
1470
- say " 3. After that: mysigner ship internal --platform android", :white
1463
+ say ' 1. Build AAB: mysigner android build', :white
1464
+ say ' 2. Upload first build manually in Play Console (one-time requirement)', :white
1465
+ say ' 3. After that: mysigner ship internal --platform android', :white
1471
1466
  end
1472
1467
  return
1473
1468
  end
@@ -1476,7 +1471,7 @@ module Mysigner
1476
1471
  end
1477
1472
 
1478
1473
  # Register the app
1479
- say "🔗 Registering with My Signer...", :cyan
1474
+ say '🔗 Registering with My Signer...', :cyan
1480
1475
 
1481
1476
  begin
1482
1477
  response = client.post(
@@ -1488,21 +1483,20 @@ module Mysigner
1488
1483
  )
1489
1484
 
1490
1485
  app = response[:data]['android_app'] || response[:data]
1491
- say "✓ App registered successfully!", :green
1492
- say ""
1493
- say "Details:", :bold
1486
+ say '✓ App registered successfully!', :green
1487
+ say ''
1488
+ say 'Details:', :bold
1494
1489
  say " ID: #{app['id']}"
1495
1490
  say " Name: #{app['name'] || '(not set)'}"
1496
1491
  say " Package: #{app['package_name']}"
1497
- say ""
1498
- say "Next steps:", :cyan
1499
- say " 1. Create app in Google Play Console", :white
1500
- say " 2. Build AAB: mysigner android build", :white
1501
- say " 3. Upload first build manually in Play Console (one-time requirement)", :white
1502
- say " 4. After that: mysigner ship internal --platform android", :white
1503
-
1492
+ say ''
1493
+ say 'Next steps:', :cyan
1494
+ say ' 1. Create app in Google Play Console', :white
1495
+ say ' 2. Build AAB: mysigner android build', :white
1496
+ say ' 3. Upload first build manually in Play Console (one-time requirement)', :white
1497
+ say ' 4. After that: mysigner ship internal --platform android', :white
1504
1498
  rescue Mysigner::ValidationError => e
1505
- error "Validation failed:"
1499
+ error 'Validation failed:'
1506
1500
  if e.details
1507
1501
  e.details.each do |field, errors|
1508
1502
  say " #{field}: #{errors.join(', ')}", :red
@@ -1519,11 +1513,11 @@ module Mysigner
1519
1513
  end
1520
1514
 
1521
1515
  def android_add(config, client, package_name, name = nil)
1522
- say "📦 Registering Android app...", :cyan
1523
- say ""
1516
+ say '📦 Registering Android app...', :cyan
1517
+ say ''
1524
1518
  say " Package: #{package_name}", :white
1525
1519
  say " Name: #{name || '(will be synced from Play Store)'}", :white
1526
- say ""
1520
+ say ''
1527
1521
 
1528
1522
  begin
1529
1523
  response = client.post(
@@ -1535,25 +1529,24 @@ module Mysigner
1535
1529
  )
1536
1530
 
1537
1531
  app = response[:data]['android_app'] || response[:data]
1538
- say "✓ App registered successfully!", :green
1539
- say ""
1540
- say "Details:", :bold
1532
+ say '✓ App registered successfully!', :green
1533
+ say ''
1534
+ say 'Details:', :bold
1541
1535
  say " ID: #{app['id']}"
1542
1536
  say " Name: #{app['name'] || '(not set)'}"
1543
1537
  say " Package: #{app['package_name']}"
1544
- say ""
1545
- say "Next steps:", :cyan
1546
- if (app['builds_count'] || 0) > 0
1547
- say " • Ship to Play Store: mysigner ship internal --platform android", :white
1538
+ say ''
1539
+ say 'Next steps:', :cyan
1540
+ if (app['builds_count'] || 0).positive?
1541
+ say ' • Ship to Play Store: mysigner ship internal --platform android', :white
1548
1542
  else
1549
- say " 1. Create app in Google Play Console (if not done)", :white
1550
- say " 2. Build AAB: mysigner android build", :white
1551
- say " 3. Upload first build manually in Play Console", :white
1552
- say " 4. Then use: mysigner ship internal --platform android", :white
1543
+ say ' 1. Create app in Google Play Console (if not done)', :white
1544
+ say ' 2. Build AAB: mysigner android build', :white
1545
+ say ' 3. Upload first build manually in Play Console', :white
1546
+ say ' 4. Then use: mysigner ship internal --platform android', :white
1553
1547
  end
1554
-
1555
1548
  rescue Mysigner::ValidationError => e
1556
- error "Validation failed:"
1549
+ error 'Validation failed:'
1557
1550
  if e.details
1558
1551
  e.details.each do |field, errors|
1559
1552
  say " #{field}: #{errors.join(', ')}", :red
@@ -1564,10 +1557,10 @@ module Mysigner
1564
1557
  say " Suggestion: #{e.suggestion}", :yellow if e.suggestion
1565
1558
  exit 1
1566
1559
  rescue Mysigner::ClientError => e
1567
- if e.message.include?("already exists") || e.message.include?("taken")
1568
- error "An app with this package name already exists"
1569
- say ""
1570
- say "List your apps with: mysigner android list", :yellow
1560
+ if e.message.include?('already exists') || e.message.include?('taken')
1561
+ error 'An app with this package name already exists'
1562
+ say ''
1563
+ say 'List your apps with: mysigner android list', :yellow
1571
1564
  else
1572
1565
  error "Failed to register app: #{e.message}"
1573
1566
  end
@@ -1576,32 +1569,32 @@ module Mysigner
1576
1569
  end
1577
1570
 
1578
1571
  def android_build
1579
- say "🔨 Building Android App Bundle (AAB)...", :cyan
1580
- say ""
1572
+ say '🔨 Building Android App Bundle (AAB)...', :cyan
1573
+ say ''
1581
1574
 
1582
1575
  begin
1583
1576
  require_relative '../build/android_parser'
1584
1577
  require_relative '../signing/keystore_manager'
1585
-
1578
+
1586
1579
  project_dir = Dir.pwd
1587
1580
  is_expo = expo_project?(project_dir)
1588
-
1581
+
1589
1582
  # For Expo, we may need to regenerate android folder with correct versionCode
1590
1583
  # Get package name from app.json first if Expo
1591
1584
  if is_expo
1592
1585
  expo_config = parse_expo_config(project_dir)
1593
1586
  package_name = expo_config[:package_name]
1594
1587
  local_version_code = expo_config[:version_code] || 1
1595
- version_name = expo_config[:version] || "1.0.0"
1596
-
1588
+ version_name = expo_config[:version] || '1.0.0'
1589
+
1597
1590
  # Check highest version code from API
1598
1591
  highest_version_code = fetch_highest_version_code(package_name)
1599
1592
  version_code = local_version_code
1600
1593
  needs_increment = highest_version_code && local_version_code <= highest_version_code
1601
-
1594
+
1602
1595
  if needs_increment
1603
1596
  version_code = highest_version_code + 1
1604
-
1597
+
1605
1598
  # Check if android folder already has the correct versionCode
1606
1599
  android_dir = File.join(project_dir, 'android')
1607
1600
  current_android_version = nil
@@ -1609,49 +1602,54 @@ module Mysigner
1609
1602
  build_gradle = File.join(android_dir, 'app', 'build.gradle')
1610
1603
  if File.exist?(build_gradle)
1611
1604
  content = File.read(build_gradle)
1612
- if content =~ /versionCode\s+(\d+)/
1613
- current_android_version = $1.to_i
1614
- end
1605
+ current_android_version = ::Regexp.last_match(1).to_i if content =~ /versionCode\s+(\d+)/
1615
1606
  end
1616
1607
  end
1617
-
1618
- say "🔧 Framework: Expo (React Native)", :white
1608
+
1609
+ say '🔧 Framework: Expo (React Native)', :white
1619
1610
  say "📦 Package: #{package_name}", :white
1620
-
1611
+
1621
1612
  if current_android_version == version_code
1622
1613
  # Android folder already has correct version, no need to regenerate
1623
1614
  say "🔢 Version: #{version_name} (#{version_code})", :white
1624
- say " ↳ Already at correct version code", :green
1615
+ say ' ↳ Already at correct version code', :green
1625
1616
  else
1626
1617
  say "🔢 Version: #{version_name} (#{local_version_code} → #{version_code})", :white
1627
1618
  say " ↳ Auto-incremented (#{highest_version_code} already on Play Store)", :yellow
1628
- say ""
1629
-
1619
+ say ''
1620
+
1630
1621
  # Regenerate android folder with new versionCode
1631
1622
  say "🔄 Regenerating android folder with version code #{version_code}...", :yellow
1632
1623
  regenerate_expo_android(project_dir, version_code)
1633
1624
  end
1634
1625
  end
1635
1626
  end
1636
-
1627
+
1637
1628
  # Now detect project (android folder should exist)
1638
1629
  project_info = Build::Detector.detect_android(project_dir)
1639
1630
  framework = project_info[:framework]
1640
1631
  parser = Build::AndroidParser.new(project_info)
1641
-
1632
+
1642
1633
  package_name ||= parser.application_id
1643
1634
  version_name ||= parser.version_name
1644
1635
  local_version_code ||= parser.version_code.to_i
1645
1636
  android_dir = project_info[:android_directory] || File.join(project_info[:directory], 'android')
1646
1637
 
1647
1638
  # For non-Expo, check version code now
1648
- unless is_expo
1639
+ if is_expo
1640
+ # Expo - already printed above, just show if no increment was needed
1641
+ unless needs_increment
1642
+ say '🔧 Framework: Expo (React Native)', :white
1643
+ say "📦 Package: #{package_name}", :white
1644
+ say "🔢 Version: #{version_name} (#{version_code})", :white
1645
+ end
1646
+ else
1649
1647
  say "🔧 Framework: #{framework.to_s.gsub('_', ' ').capitalize}", :white
1650
1648
  say "📦 Package: #{package_name}", :white
1651
-
1649
+
1652
1650
  highest_version_code = fetch_highest_version_code(package_name)
1653
1651
  version_code = local_version_code
1654
-
1652
+
1655
1653
  if highest_version_code && local_version_code <= highest_version_code
1656
1654
  version_code = highest_version_code + 1
1657
1655
  say "🔢 Version: #{version_name} (#{local_version_code} → #{version_code})", :white
@@ -1659,84 +1657,78 @@ module Mysigner
1659
1657
  else
1660
1658
  say "🔢 Version: #{version_name} (#{version_code})", :white
1661
1659
  end
1662
- else
1663
- # Expo - already printed above, just show if no increment was needed
1664
- unless needs_increment
1665
- say "🔧 Framework: Expo (React Native)", :white
1666
- say "📦 Package: #{package_name}", :white
1667
- say "🔢 Version: #{version_name} (#{version_code})", :white
1668
- end
1669
1660
  end
1670
- say ""
1661
+ say ''
1671
1662
 
1672
1663
  # Try to get keystore from MySigner
1673
1664
  keystore_info = fetch_keystore_for_build(package_name)
1674
1665
  if keystore_info
1675
1666
  say "🔐 Keystore: #{keystore_info[:name]}", :green
1676
1667
  else
1677
- say "⚠️ No keystore configured - will use debug signing", :yellow
1668
+ say '⚠️ No keystore configured - will use debug signing', :yellow
1678
1669
  say " Run 'mysigner android init' to set up release signing", :yellow
1679
1670
  end
1680
- say ""
1681
- say "⏱️ This may take a few minutes...", :yellow
1682
- say ""
1671
+ say ''
1672
+ say '⏱️ This may take a few minutes...', :yellow
1673
+ say ''
1683
1674
 
1684
1675
  # Build based on framework (pass version_code override if incremented)
1685
1676
  # For Expo, we already regenerated with correct version, so no override needed
1686
1677
  version_code_override = nil
1687
1678
  unless is_expo
1688
- version_code_override = (version_code != local_version_code) ? version_code : nil
1679
+ version_code_override = version_code == local_version_code ? nil : version_code
1689
1680
  end
1690
-
1681
+
1691
1682
  aab_path = case framework
1692
- when :flutter
1693
- build_flutter_aab(project_dir, keystore_info, version_code_override)
1694
- when :maui, :xamarin, :xamarin_forms
1695
- build_dotnet_aab(project_dir, project_info[:csproj_path], framework, keystore_info, version_code_override)
1696
- when :react_native, :capacitor, :native
1697
- build_gradle_aab(android_dir, framework, keystore_info, version_code_override)
1698
- else
1699
- build_gradle_aab(android_dir, framework, keystore_info, version_code_override)
1700
- end
1683
+ when :flutter
1684
+ build_flutter_aab(project_dir, keystore_info, version_code_override)
1685
+ when :maui, :xamarin, :xamarin_forms
1686
+ build_dotnet_aab(project_dir, project_info[:csproj_path], framework, keystore_info,
1687
+ version_code_override)
1688
+ when :react_native, :capacitor, :native
1689
+ build_gradle_aab(android_dir, framework, keystore_info, version_code_override)
1690
+ else
1691
+ build_gradle_aab(android_dir, framework, keystore_info, version_code_override)
1692
+ end
1701
1693
 
1702
1694
  unless aab_path && File.exist?(aab_path)
1703
- error "AAB file not found after build"
1704
- say "Check build output for errors.", :yellow
1695
+ error 'AAB file not found after build'
1696
+ say 'Check build output for errors.', :yellow
1705
1697
  exit 1
1706
1698
  end
1707
1699
 
1708
- say ""
1709
- say "=" * 80, :green
1710
- say "✓ Build complete!", :green
1711
- say "=" * 80, :green
1712
- say ""
1700
+ say ''
1701
+ say '=' * 80, :green
1702
+ say '✓ Build complete!', :green
1703
+ say '=' * 80, :green
1704
+ say ''
1713
1705
  say "📦 AAB: #{aab_path}", :cyan
1714
1706
  say "📊 Size: #{format_bytes(File.size(aab_path))}", :cyan
1715
- say ""
1716
- say "Next step:", :bold
1717
- say " Upload this AAB to Google Play Console → Internal testing → Create release", :white
1718
- say ""
1719
- say "After uploading, you can use:", :cyan
1720
- say " mysigner ship internal --platform android", :green
1721
- say ""
1722
-
1707
+ say ''
1708
+ say 'Next step:', :bold
1709
+ say ' Upload this AAB to Google Play Console → Internal testing → Create release', :white
1710
+ say ''
1711
+ say 'After uploading, you can use:', :cyan
1712
+ say ' mysigner ship internal --platform android', :green
1713
+ say ''
1714
+
1723
1715
  # Open the folder containing the AAB
1724
1716
  aab_dir = File.dirname(aab_path)
1725
- say "📂 Opening folder...", :yellow
1726
- if RUBY_PLATFORM =~ /darwin/
1717
+ say '📂 Opening folder...', :yellow
1718
+ case RUBY_PLATFORM
1719
+ when /darwin/
1727
1720
  system('open', aab_dir)
1728
- elsif RUBY_PLATFORM =~ /linux/
1721
+ when /linux/
1729
1722
  system('xdg-open', aab_dir)
1730
- elsif RUBY_PLATFORM =~ /mingw|mswin/
1723
+ when /mingw|mswin/
1731
1724
  system('explorer', aab_dir.gsub('/', '\\'))
1732
1725
  end
1733
-
1734
1726
  rescue Build::Detector::NoProjectError => e
1735
1727
  error e.message
1736
- say ""
1737
- say "Run this command from an Android project directory.", :yellow
1728
+ say ''
1729
+ say 'Run this command from an Android project directory.', :yellow
1738
1730
  exit 1
1739
- rescue => e
1731
+ rescue StandardError => e
1740
1732
  error "Build failed: #{e.message}"
1741
1733
  exit 1
1742
1734
  end
@@ -1745,19 +1737,17 @@ module Mysigner
1745
1737
  def build_flutter_aab(project_dir, keystore_info = nil, version_code_override = nil)
1746
1738
  # Check for flutter
1747
1739
  unless system('which flutter > /dev/null 2>&1')
1748
- error "Flutter not found in PATH"
1749
- say "Install Flutter: https://flutter.dev/docs/get-started/install", :yellow
1740
+ error 'Flutter not found in PATH'
1741
+ say 'Install Flutter: https://flutter.dev/docs/get-started/install', :yellow
1750
1742
  exit 1
1751
1743
  end
1752
1744
 
1753
1745
  Dir.chdir(project_dir) do
1754
1746
  args = ['flutter', 'build', 'appbundle', '--release']
1755
-
1747
+
1756
1748
  # Add version code override
1757
- if version_code_override
1758
- args += ['--build-number', version_code_override.to_s]
1759
- end
1760
-
1749
+ args += ['--build-number', version_code_override.to_s] if version_code_override
1750
+
1761
1751
  # Add signing if keystore provided (Flutter reads key.properties from android/)
1762
1752
  if keystore_info
1763
1753
  # Create key.properties for Flutter
@@ -1769,10 +1759,10 @@ module Mysigner
1769
1759
  storeFile=#{keystore_info[:path]}
1770
1760
  PROPS
1771
1761
  end
1772
-
1762
+
1773
1763
  success = system(*args)
1774
1764
  unless success
1775
- error "Flutter build failed"
1765
+ error 'Flutter build failed'
1776
1766
  exit 1
1777
1767
  end
1778
1768
  end
@@ -1798,46 +1788,41 @@ module Mysigner
1798
1788
  app_json_path = File.join(project_dir, 'app.json')
1799
1789
  android_dir = File.join(project_dir, 'android')
1800
1790
  local_props_path = File.join(android_dir, 'local.properties')
1801
-
1791
+
1802
1792
  # Preserve local.properties if it exists
1803
1793
  local_props_content = File.read(local_props_path) if File.exist?(local_props_path)
1804
-
1794
+
1805
1795
  # Read original app.json
1806
1796
  original_content = File.read(app_json_path)
1807
1797
  config = JSON.parse(original_content)
1808
-
1798
+
1809
1799
  # Set the new versionCode
1810
1800
  config['expo'] ||= {}
1811
1801
  config['expo']['android'] ||= {}
1812
1802
  config['expo']['android']['versionCode'] = new_version_code
1813
-
1803
+
1814
1804
  # Write modified app.json
1815
1805
  File.write(app_json_path, JSON.pretty_generate(config))
1816
-
1806
+
1817
1807
  begin
1818
1808
  # Delete existing android folder
1819
- if Dir.exist?(android_dir)
1820
- FileUtils.rm_rf(android_dir)
1821
- end
1822
-
1809
+
1810
+ FileUtils.rm_rf(android_dir)
1811
+
1823
1812
  # Run expo prebuild
1824
1813
  Dir.chdir(project_dir) do
1825
1814
  success = system('npx', 'expo', 'prebuild', '--platform', 'android', '--clean')
1826
- unless success
1827
- raise "expo prebuild failed"
1828
- end
1815
+ raise 'expo prebuild failed' unless success
1829
1816
  end
1830
-
1817
+
1831
1818
  # Restore local.properties if we had one, or create default
1832
1819
  if local_props_content
1833
1820
  File.write(local_props_path, local_props_content)
1834
1821
  else
1835
1822
  # Try to detect Android SDK and create local.properties
1836
- sdk_path = ENV['ANDROID_HOME'] || ENV['ANDROID_SDK_ROOT'] ||
1823
+ sdk_path = ENV['ANDROID_HOME'] || ENV['ANDROID_SDK_ROOT'] ||
1837
1824
  File.expand_path('~/Library/Android/sdk')
1838
- if Dir.exist?(sdk_path)
1839
- File.write(local_props_path, "sdk.dir=#{sdk_path}\n")
1840
- end
1825
+ File.write(local_props_path, "sdk.dir=#{sdk_path}\n") if Dir.exist?(sdk_path)
1841
1826
  end
1842
1827
  ensure
1843
1828
  # Restore original app.json
@@ -1848,19 +1833,22 @@ module Mysigner
1848
1833
  def fetch_highest_version_code(package_name)
1849
1834
  config = Mysigner::Config.new
1850
1835
  return nil unless config.exists?
1836
+
1851
1837
  config.load
1852
1838
  return nil unless config.api_token && config.organization_id
1853
1839
 
1854
- client = Mysigner::Client.new(api_url: config.api_url, api_token: config.api_token, user_email: config.user_email)
1840
+ client = Mysigner::Client.new(api_url: config.api_url, api_token: config.api_token,
1841
+ user_email: config.user_email)
1855
1842
 
1856
1843
  # Find app by package name
1857
1844
  response = client.get("/api/v1/organizations/#{config.organization_id}/android_apps")
1858
1845
  apps = response[:data]['android_apps'] || []
1859
1846
  app = apps.find { |a| a['package_name'] == package_name }
1860
-
1847
+
1861
1848
  return app['highest_version_code'].to_i if app && app['highest_version_code']
1849
+
1862
1850
  nil
1863
- rescue => e
1851
+ rescue StandardError
1864
1852
  # Silently fail - we'll use local version
1865
1853
  nil
1866
1854
  end
@@ -1868,10 +1856,12 @@ module Mysigner
1868
1856
  def fetch_keystore_for_build(package_name)
1869
1857
  config = Mysigner::Config.new
1870
1858
  return nil unless config.exists?
1859
+
1871
1860
  config.load
1872
1861
  return nil unless config.api_token && config.organization_id
1873
1862
 
1874
- client = Mysigner::Client.new(api_url: config.api_url, api_token: config.api_token, user_email: config.user_email)
1863
+ client = Mysigner::Client.new(api_url: config.api_url, api_token: config.api_token,
1864
+ user_email: config.user_email)
1875
1865
  keystore_manager = Signing::KeystoreManager.new(client, config.organization_id)
1876
1866
 
1877
1867
  # Find app by package name to get its keystore
@@ -1909,59 +1899,57 @@ module Mysigner
1909
1899
  end
1910
1900
 
1911
1901
  nil
1912
- rescue => e
1902
+ rescue StandardError
1913
1903
  # Silently fail - we'll use debug signing
1914
1904
  nil
1915
1905
  end
1916
1906
 
1917
- def build_dotnet_aab(project_dir, csproj_path, framework, keystore_info = nil, version_code_override = nil)
1907
+ def build_dotnet_aab(project_dir, _csproj_path, framework, keystore_info = nil, version_code_override = nil)
1918
1908
  # Check for dotnet
1919
1909
  unless system('which dotnet > /dev/null 2>&1')
1920
- error ".NET SDK not found in PATH"
1921
- say "Install .NET: https://dotnet.microsoft.com/download", :yellow
1910
+ error '.NET SDK not found in PATH'
1911
+ say 'Install .NET: https://dotnet.microsoft.com/download', :yellow
1922
1912
  exit 1
1923
1913
  end
1924
1914
 
1925
1915
  Dir.chdir(project_dir) do
1926
1916
  base_args = []
1927
-
1917
+
1928
1918
  # Add signing args if keystore provided
1929
1919
  if keystore_info
1930
1920
  base_args += [
1931
- "-p:AndroidKeyStore=true",
1921
+ '-p:AndroidKeyStore=true',
1932
1922
  "-p:AndroidSigningKeyStore=#{keystore_info[:path]}",
1933
1923
  "-p:AndroidSigningKeyAlias=#{keystore_info[:key_alias]}",
1934
1924
  "-p:AndroidSigningKeyPass=#{keystore_info[:key_password]}",
1935
1925
  "-p:AndroidSigningStorePass=#{keystore_info[:password]}"
1936
1926
  ]
1937
1927
  end
1938
-
1928
+
1939
1929
  # Add version code override
1940
- if version_code_override
1941
- base_args << "-p:ApplicationVersion=#{version_code_override}"
1942
- end
1930
+ base_args << "-p:ApplicationVersion=#{version_code_override}" if version_code_override
1943
1931
 
1944
1932
  # MAUI uses dotnet publish with Android target
1945
- if framework == :maui
1946
- success = system(
1947
- 'dotnet', 'publish',
1948
- '-f', 'net8.0-android',
1949
- '-c', 'Release',
1950
- '-p:AndroidPackageFormat=aab',
1951
- *base_args
1952
- )
1953
- else
1954
- # Xamarin uses msbuild
1955
- success = system(
1956
- 'dotnet', 'build',
1957
- '-c', 'Release',
1958
- '-p:AndroidPackageFormat=aab',
1959
- *base_args
1960
- )
1961
- end
1933
+ success = if framework == :maui
1934
+ system(
1935
+ 'dotnet', 'publish',
1936
+ '-f', 'net8.0-android',
1937
+ '-c', 'Release',
1938
+ '-p:AndroidPackageFormat=aab',
1939
+ *base_args
1940
+ )
1941
+ else
1942
+ # Xamarin uses msbuild
1943
+ system(
1944
+ 'dotnet', 'build',
1945
+ '-c', 'Release',
1946
+ '-p:AndroidPackageFormat=aab',
1947
+ *base_args
1948
+ )
1949
+ end
1962
1950
 
1963
1951
  unless success
1964
- error ".NET build failed"
1952
+ error '.NET build failed'
1965
1953
  exit 1
1966
1954
  end
1967
1955
  end
@@ -1983,14 +1971,14 @@ module Mysigner
1983
1971
  when :capacitor
1984
1972
  say "Run 'npx cap sync android' first.", :yellow
1985
1973
  else
1986
- say "Ensure the android/ folder has gradlew.", :yellow
1974
+ say 'Ensure the android/ folder has gradlew.', :yellow
1987
1975
  end
1988
1976
  exit 1
1989
1977
  end
1990
1978
 
1991
1979
  # Build gradle command with signing via command-line properties
1992
1980
  gradle_args = ['./gradlew', 'bundleRelease', '--warning-mode=all']
1993
-
1981
+
1994
1982
  if keystore_info
1995
1983
  # Pass signing config via command-line properties (no file modification needed)
1996
1984
  gradle_args += [
@@ -2000,16 +1988,14 @@ module Mysigner
2000
1988
  "-Pandroid.injected.signing.key.password=#{keystore_info[:key_password]}"
2001
1989
  ]
2002
1990
  end
2003
-
1991
+
2004
1992
  # Pass version code override if provided (no file modification needed)
2005
- if version_code_override
2006
- gradle_args << "-PversionCode=#{version_code_override}"
2007
- end
1993
+ gradle_args << "-PversionCode=#{version_code_override}" if version_code_override
2008
1994
 
2009
1995
  Dir.chdir(android_dir) do
2010
1996
  success = system(*gradle_args)
2011
1997
  unless success
2012
- error "Gradle build failed"
1998
+ error 'Gradle build failed'
2013
1999
  exit 1
2014
2000
  end
2015
2001
  end
@@ -2049,11 +2035,9 @@ module Mysigner
2049
2035
  content = File.read(app_config_path)
2050
2036
  # Basic regex extraction for package name
2051
2037
  if content =~ /android\s*:\s*\{[^}]*package\s*:\s*["']([^"']+)["']/m
2052
- package_name = $1
2038
+ package_name = ::Regexp.last_match(1)
2053
2039
  name = nil
2054
- if content =~ /name\s*:\s*["']([^"']+)["']/
2055
- name = $1
2056
- end
2040
+ name = ::Regexp.last_match(1) if content =~ /name\s*:\s*["']([^"']+)["']/
2057
2041
  return {
2058
2042
  package_name: package_name,
2059
2043
  bundle_id: nil,
@@ -2069,7 +2053,7 @@ module Mysigner
2069
2053
 
2070
2054
  # ==================== BUNDLE IDS ====================
2071
2055
 
2072
- desc "bundleid SUBCOMMAND", "Register and manage iOS Bundle IDs"
2056
+ desc 'bundleid SUBCOMMAND', 'Register and manage iOS Bundle IDs'
2073
2057
  long_desc <<~DESC
2074
2058
  Register and manage iOS Bundle IDs in App Store Connect.
2075
2059
 
@@ -2116,10 +2100,10 @@ module Mysigner
2116
2100
  case action
2117
2101
  when 'register'
2118
2102
  if args.empty?
2119
- error "Usage: mysigner bundleid register IDENTIFIER [NAME]"
2120
- say ""
2121
- say "Example: mysigner bundleid register com.company.myapp", :yellow
2122
- say "Example: mysigner bundleid register com.company.myapp.widget \"My Widget\"", :yellow
2103
+ error 'Usage: mysigner bundleid register IDENTIFIER [NAME]'
2104
+ say ''
2105
+ say 'Example: mysigner bundleid register com.company.myapp', :yellow
2106
+ say 'Example: mysigner bundleid register com.company.myapp.widget "My Widget"', :yellow
2123
2107
  exit 1
2124
2108
  end
2125
2109
 
@@ -2130,19 +2114,19 @@ module Mysigner
2130
2114
  # Validate bundle ID format
2131
2115
  unless identifier =~ /^[a-zA-Z][a-zA-Z0-9.-]*\.[a-zA-Z][a-zA-Z0-9.-]*$/
2132
2116
  error "Invalid Bundle ID format: #{identifier}"
2133
- say ""
2134
- say "Bundle IDs must:", :yellow
2135
- say " • Start with a letter", :cyan
2136
- say " • Use reverse domain notation (e.g., com.company.app)", :cyan
2137
- say " • Contain only letters, numbers, hyphens, and periods", :cyan
2117
+ say ''
2118
+ say 'Bundle IDs must:', :yellow
2119
+ say ' • Start with a letter', :cyan
2120
+ say ' • Use reverse domain notation (e.g., com.company.app)', :cyan
2121
+ say ' • Contain only letters, numbers, hyphens, and periods', :cyan
2138
2122
  exit 1
2139
2123
  end
2140
2124
 
2141
- say "🔗 Registering Bundle ID...", :cyan
2142
- say ""
2125
+ say '🔗 Registering Bundle ID...', :cyan
2126
+ say ''
2143
2127
  say " Identifier: #{identifier}", :white
2144
2128
  say " Name: #{name}", :white
2145
- say ""
2129
+ say ''
2146
2130
 
2147
2131
  begin
2148
2132
  response = client.post(
@@ -2155,18 +2139,18 @@ module Mysigner
2155
2139
  )
2156
2140
 
2157
2141
  bundle_id_data = response[:data]['bundle_id'] || response[:data]
2158
- say "✓ Bundle ID registered successfully!", :green
2159
- say ""
2160
- say "Details:", :bold
2142
+ say '✓ Bundle ID registered successfully!', :green
2143
+ say ''
2144
+ say 'Details:', :bold
2161
2145
  say " Identifier: #{bundle_id_data['identifier'] || identifier}"
2162
2146
  say " Name: #{bundle_id_data['name'] || name}"
2163
- say ""
2164
- say "Next steps:", :cyan
2165
- say " 1. Sync to update local cache: mysigner sync ios", :white
2166
- say " 2. Create a provisioning profile: mysigner doctor (will auto-create)", :white
2167
- say " 3. Or run: mysigner signing configure", :white
2147
+ say ''
2148
+ say 'Next steps:', :cyan
2149
+ say ' 1. Sync to update local cache: mysigner sync ios', :white
2150
+ say ' 2. Create a provisioning profile: mysigner doctor (will auto-create)', :white
2151
+ say ' 3. Or run: mysigner signing configure', :white
2168
2152
  rescue Mysigner::ValidationError => e
2169
- error "Validation failed:"
2153
+ error 'Validation failed:'
2170
2154
  if e.details
2171
2155
  e.details.each do |field, errors|
2172
2156
  say " #{field}: #{errors.join(', ')}", :red
@@ -2177,25 +2161,25 @@ module Mysigner
2177
2161
  say " Suggestion: #{e.suggestion}", :yellow if e.suggestion
2178
2162
  exit 1
2179
2163
  rescue Mysigner::ClientError => e
2180
- if e.message.include?("already exists") || e.message.include?("ENTITY_ERROR.ATTRIBUTE.INVALID.DUPLICATE")
2164
+ if e.message.include?('already exists') || e.message.include?('ENTITY_ERROR.ATTRIBUTE.INVALID.DUPLICATE')
2181
2165
  say "ℹ️ Bundle ID already registered: #{identifier}", :yellow
2182
- say ""
2183
- say "This Bundle ID already exists in App Store Connect.", :white
2166
+ say ''
2167
+ say 'This Bundle ID already exists in App Store Connect.', :white
2184
2168
  say "Run 'mysigner sync ios' to update your local cache.", :cyan
2185
2169
  else
2186
2170
  error "Failed to register Bundle ID: #{e.message}"
2187
- say ""
2188
- say "Common issues:", :yellow
2189
- say " • Bundle ID already exists (check App Store Connect)", :cyan
2190
- say " • Invalid format (must be like com.company.app)", :cyan
2191
- say " • API credentials may not have permission", :cyan
2171
+ say ''
2172
+ say 'Common issues:', :yellow
2173
+ say ' • Bundle ID already exists (check App Store Connect)', :cyan
2174
+ say ' • Invalid format (must be like com.company.app)', :cyan
2175
+ say ' • API credentials may not have permission', :cyan
2192
2176
  end
2193
2177
  exit 1
2194
2178
  end
2195
2179
 
2196
2180
  when 'list'
2197
- say "📦 Registered Bundle IDs", :cyan
2198
- say ""
2181
+ say '📦 Registered Bundle IDs', :cyan
2182
+ say ''
2199
2183
 
2200
2184
  begin
2201
2185
  response = client.get(
@@ -2204,16 +2188,16 @@ module Mysigner
2204
2188
  bundle_ids = response[:data]['bundle_ids'] || response[:data] || []
2205
2189
 
2206
2190
  if bundle_ids.empty?
2207
- say " No Bundle IDs found", :yellow
2208
- say ""
2209
- say " Register one with: mysigner bundleid register com.company.app", :cyan
2191
+ say ' No Bundle IDs found', :yellow
2192
+ say ''
2193
+ say ' Register one with: mysigner bundleid register com.company.app', :cyan
2210
2194
  else
2211
2195
  bundle_ids.each do |bid|
2212
2196
  identifier = bid['identifier'] || bid['bundle_id']
2213
2197
  name = bid['name'] || 'N/A'
2214
2198
  say " • #{name}", :green
2215
2199
  say " Identifier: #{identifier}"
2216
- say ""
2200
+ say ''
2217
2201
  end
2218
2202
  end
2219
2203
  rescue Mysigner::ClientError => e
@@ -2223,17 +2207,17 @@ module Mysigner
2223
2207
 
2224
2208
  else
2225
2209
  error "Unknown action: #{action}"
2226
- say ""
2227
- say "Available actions:", :yellow
2228
- say " mysigner bundleid register IDENTIFIER [NAME]", :cyan
2229
- say " mysigner bundleid list", :cyan
2210
+ say ''
2211
+ say 'Available actions:', :yellow
2212
+ say ' mysigner bundleid register IDENTIFIER [NAME]', :cyan
2213
+ say ' mysigner bundleid list', :cyan
2230
2214
  exit 1
2231
2215
  end
2232
2216
  end
2233
2217
 
2234
2218
  # ==================== APPS (iOS + Android) ====================
2235
2219
 
2236
- desc "apps", "List apps from App Store Connect and/or Google Play"
2220
+ desc 'apps', 'List apps from App Store Connect and/or Google Play'
2237
2221
  long_desc <<~DESC
2238
2222
  List apps synced from app stores.
2239
2223
 
@@ -2268,8 +2252,8 @@ module Mysigner
2268
2252
 
2269
2253
  # iOS Apps
2270
2254
  if show_ios
2271
- say "📱 iOS Apps", :cyan
2272
- say ""
2255
+ say '📱 iOS Apps', :cyan
2256
+ say ''
2273
2257
 
2274
2258
  begin
2275
2259
  response = client.get(
@@ -2279,78 +2263,78 @@ module Mysigner
2279
2263
  ios_apps = response[:data]['data']&.fetch('apps', nil) || []
2280
2264
 
2281
2265
  if ios_apps.empty?
2282
- say " No iOS apps found", :yellow
2283
- say ""
2266
+ say ' No iOS apps found', :yellow
2267
+ say ''
2284
2268
  say " Why don't my iOS apps appear?", :cyan
2285
- say " ─────────────────────────────", :cyan
2286
- say ""
2287
- say " Common reasons:", :yellow
2288
- say " • No apps registered in App Store Connect yet"
2289
- say " • Team ID not set on your credential"
2290
- say " • Bundle IDs exist but apps not created in App Store Connect"
2291
- say ""
2292
- say " How to register an iOS app:", :cyan
2293
- say ""
2294
- say " 1. Register a Bundle ID"
2295
- say " https://developer.apple.com/account/resources/identifiers/list"
2269
+ say ' ─────────────────────────────', :cyan
2270
+ say ''
2271
+ say ' Common reasons:', :yellow
2272
+ say ' • No apps registered in App Store Connect yet'
2273
+ say ' • Team ID not set on your credential'
2274
+ say ' • Bundle IDs exist but apps not created in App Store Connect'
2275
+ say ''
2276
+ say ' How to register an iOS app:', :cyan
2277
+ say ''
2278
+ say ' 1. Register a Bundle ID'
2279
+ say ' https://developer.apple.com/account/resources/identifiers/list'
2296
2280
  say " Click '+' → App IDs → Enter your Bundle ID (e.g., com.company.appname)"
2297
- say ""
2298
- say " 2. Create the app in App Store Connect"
2299
- say " https://appstoreconnect.apple.com/apps"
2281
+ say ''
2282
+ say ' 2. Create the app in App Store Connect'
2283
+ say ' https://appstoreconnect.apple.com/apps'
2300
2284
  say " My Apps → '+' → New App → Select your Bundle ID"
2301
- say ""
2302
- say " 3. Sync your organization"
2303
- say " Run: ", :white
2304
- say "mysigner sync ios", :green
2305
- say ""
2306
- say " 💡 Team ID tip:", :yellow
2285
+ say ''
2286
+ say ' 3. Sync your organization'
2287
+ say ' Run: ', :white
2288
+ say 'mysigner sync ios', :green
2289
+ say ''
2290
+ say ' 💡 Team ID tip:', :yellow
2307
2291
  say " Apple's API doesn't expose Team ID. Set it manually in the web dashboard."
2308
- say " Find yours at: https://developer.apple.com/account/#!/membership/"
2309
- say ""
2292
+ say ' Find yours at: https://developer.apple.com/account/#!/membership/'
2293
+ say ''
2310
2294
  else
2311
2295
  ios_apps.each do |app|
2312
2296
  say " • #{app['name'] || app['bundle_id']}", :green
2313
2297
  say " Bundle ID: #{app['bundle_id']}"
2314
- say ""
2298
+ say ''
2315
2299
  end
2316
2300
  end
2317
2301
  rescue Mysigner::ClientError => e
2318
2302
  say " Could not fetch iOS apps: #{e.message}", :yellow
2319
2303
  end
2320
- say ""
2304
+ say ''
2321
2305
  end
2322
2306
 
2323
2307
  # Android Apps
2324
- if show_android
2325
- say "🤖 Android Apps", :cyan
2326
- say ""
2308
+ return unless show_android
2327
2309
 
2328
- begin
2329
- response = client.get(
2330
- "/api/v1/organizations/#{config.current_organization_id}/android_apps",
2331
- params: params
2332
- )
2333
- android_apps = response[:data]['android_apps'] || []
2310
+ say '🤖 Android Apps', :cyan
2311
+ say ''
2334
2312
 
2335
- if android_apps.empty?
2336
- say " No Android apps found", :yellow
2337
- say " Sync with: mysigner sync android", :yellow
2338
- else
2339
- android_apps.each do |app|
2340
- say " • #{app['name'] || app['package_name']}", :green
2341
- say " Package: #{app['package_name']}"
2342
- say ""
2343
- end
2313
+ begin
2314
+ response = client.get(
2315
+ "/api/v1/organizations/#{config.current_organization_id}/android_apps",
2316
+ params: params
2317
+ )
2318
+ android_apps = response[:data]['android_apps'] || []
2319
+
2320
+ if android_apps.empty?
2321
+ say ' No Android apps found', :yellow
2322
+ say ' Sync with: mysigner sync android', :yellow
2323
+ else
2324
+ android_apps.each do |app|
2325
+ say " • #{app['name'] || app['package_name']}", :green
2326
+ say " Package: #{app['package_name']}"
2327
+ say ''
2344
2328
  end
2345
- rescue Mysigner::ClientError => e
2346
- say " Could not fetch Android apps: #{e.message}", :yellow
2347
2329
  end
2330
+ rescue Mysigner::ClientError => e
2331
+ say " Could not fetch Android apps: #{e.message}", :yellow
2348
2332
  end
2349
2333
  end
2350
2334
 
2351
2335
  # ==================== MERCHANT IDS (Apple Pay) ====================
2352
2336
 
2353
- desc "merchant-ids", "List Apple Pay Merchant IDs"
2337
+ desc 'merchant-ids', 'List Apple Pay Merchant IDs'
2354
2338
  method_option :search, type: :string, aliases: '-q', desc: 'Search by identifier or name'
2355
2339
  method_option :page, type: :numeric, default: 1, desc: 'Page number'
2356
2340
  method_option :per_page, type: :numeric, default: 50, desc: 'Items per page'
@@ -2358,8 +2342,8 @@ module Mysigner
2358
2342
  config = load_config
2359
2343
  client = create_client(config)
2360
2344
 
2361
- say "💳 Merchant IDs", :cyan
2362
- say ""
2345
+ say '💳 Merchant IDs', :cyan
2346
+ say ''
2363
2347
 
2364
2348
  params = {
2365
2349
  page: options[:page],
@@ -2376,19 +2360,20 @@ module Mysigner
2376
2360
  pagination = response[:data]['pagination']
2377
2361
 
2378
2362
  if merchant_ids.empty?
2379
- say " No Merchant IDs found", :yellow
2380
- say ""
2381
- say " Create one with: mysigner merchant-id create IDENTIFIER", :cyan
2363
+ say ' No Merchant IDs found', :yellow
2364
+ say ''
2365
+ say ' Create one with: mysigner merchant-id create IDENTIFIER', :cyan
2382
2366
  else
2383
2367
  merchant_ids.each do |m|
2384
2368
  say " • #{m['identifier']}", :green
2385
2369
  say " Name: #{m['name']}" if m['name'] && m['name'] != m['identifier']
2386
2370
  say " Team: #{m['team_id']}" if m['team_id']
2387
- say ""
2371
+ say ''
2388
2372
  end
2389
2373
 
2390
2374
  if pagination
2391
- say "Page #{pagination['page']} of #{pagination['total_pages']} (#{pagination['total']} total)", :yellow
2375
+ say "Page #{pagination['page']} of #{pagination['total_pages']} (#{pagination['total']} total)",
2376
+ :yellow
2392
2377
  end
2393
2378
  end
2394
2379
  rescue Mysigner::ClientError => e
@@ -2397,7 +2382,7 @@ module Mysigner
2397
2382
  end
2398
2383
  end
2399
2384
 
2400
- desc "merchant-id SUBCOMMAND", "Manage Apple Pay Merchant IDs"
2385
+ desc 'merchant-id SUBCOMMAND', 'Manage Apple Pay Merchant IDs'
2401
2386
  long_desc <<~DESC
2402
2387
  Create and delete Apple Pay Merchant IDs.
2403
2388
 
@@ -2423,21 +2408,21 @@ module Mysigner
2423
2408
  case action
2424
2409
  when 'create'
2425
2410
  if identifier.nil?
2426
- error "Usage: mysigner merchant-id create IDENTIFIER [--name NAME]"
2427
- say ""
2428
- say "Example: mysigner merchant-id create merchant.com.company.app", :yellow
2411
+ error 'Usage: mysigner merchant-id create IDENTIFIER [--name NAME]'
2412
+ say ''
2413
+ say 'Example: mysigner merchant-id create merchant.com.company.app', :yellow
2429
2414
  exit 1
2430
2415
  end
2431
2416
 
2432
2417
  unless identifier.start_with?('merchant.')
2433
2418
  error "Merchant ID must start with 'merchant.'"
2434
- say ""
2435
- say "Example: merchant.com.company.app", :cyan
2419
+ say ''
2420
+ say 'Example: merchant.com.company.app', :cyan
2436
2421
  exit 1
2437
2422
  end
2438
2423
 
2439
- say "💳 Creating Merchant ID...", :cyan
2440
- say ""
2424
+ say '💳 Creating Merchant ID...', :cyan
2425
+ say ''
2441
2426
 
2442
2427
  begin
2443
2428
  response = client.post(
@@ -2449,12 +2434,12 @@ module Mysigner
2449
2434
  )
2450
2435
 
2451
2436
  m = response[:data]['merchant_id'] || response[:data]
2452
- say "✓ Merchant ID created successfully!", :green
2453
- say ""
2437
+ say '✓ Merchant ID created successfully!', :green
2438
+ say ''
2454
2439
  say " Identifier: #{m['identifier'] || identifier}", :white
2455
2440
  say " Name: #{m['name']}", :white if m['name']
2456
2441
  rescue Mysigner::ClientError => e
2457
- if e.message.include?("already exists")
2442
+ if e.message.include?('already exists')
2458
2443
  say "ℹ️ Merchant ID already exists: #{identifier}", :yellow
2459
2444
  else
2460
2445
  error "Failed to create Merchant ID: #{e.message}"
@@ -2464,12 +2449,12 @@ module Mysigner
2464
2449
 
2465
2450
  when 'delete'
2466
2451
  if identifier.nil?
2467
- error "Usage: mysigner merchant-id delete IDENTIFIER"
2452
+ error 'Usage: mysigner merchant-id delete IDENTIFIER'
2468
2453
  exit 1
2469
2454
  end
2470
2455
 
2471
- say "💳 Deleting Merchant ID...", :cyan
2472
- say ""
2456
+ say '💳 Deleting Merchant ID...', :cyan
2457
+ say ''
2473
2458
 
2474
2459
  begin
2475
2460
  # First find the merchant ID by identifier
@@ -2497,43 +2482,43 @@ module Mysigner
2497
2482
 
2498
2483
  else
2499
2484
  error "Unknown action: #{action}"
2500
- say ""
2501
- say "Available actions:", :yellow
2502
- say " mysigner merchant-id create IDENTIFIER [--name NAME]", :cyan
2503
- say " mysigner merchant-id delete IDENTIFIER", :cyan
2485
+ say ''
2486
+ say 'Available actions:', :yellow
2487
+ say ' mysigner merchant-id create IDENTIFIER [--name NAME]', :cyan
2488
+ say ' mysigner merchant-id delete IDENTIFIER', :cyan
2504
2489
  exit 1
2505
2490
  end
2506
2491
  end
2507
2492
 
2508
2493
  # ==================== ANDROID TRACKS ====================
2509
2494
 
2510
- desc "tracks PACKAGE_NAME", "List Google Play tracks for an Android app"
2495
+ desc 'tracks PACKAGE_NAME', 'List Google Play tracks for an Android app'
2511
2496
  method_option :sort, type: :boolean, desc: 'Sort by track name'
2512
2497
  def tracks(package_name = nil)
2513
2498
  config = load_config
2514
2499
  client = create_client(config)
2515
2500
 
2516
2501
  if package_name.nil?
2517
- error "Usage: mysigner tracks PACKAGE_NAME"
2518
- say ""
2519
- say "Example: mysigner tracks com.example.myapp", :yellow
2520
- say ""
2521
- say "💡 To see your registered Android apps:", :cyan
2522
- say " mysigner apps --platform android", :cyan
2502
+ error 'Usage: mysigner tracks PACKAGE_NAME'
2503
+ say ''
2504
+ say 'Example: mysigner tracks com.example.myapp', :yellow
2505
+ say ''
2506
+ say '💡 To see your registered Android apps:', :cyan
2507
+ say ' mysigner apps --platform android', :cyan
2523
2508
  exit 1
2524
2509
  end
2525
2510
 
2526
2511
  say "🎯 Google Play Tracks for #{package_name}", :cyan
2527
- say ""
2512
+ say ''
2528
2513
 
2529
2514
  begin
2530
2515
  response = client.get("/api/v1/organizations/#{config.current_organization_id}/android_apps/package/#{package_name}/tracks")
2531
2516
  tracks = response[:data]['tracks'] || []
2532
2517
 
2533
2518
  if tracks.empty?
2534
- say "No tracks found", :yellow
2535
- say ""
2536
- say "Tracks appear after you upload your app to Google Play Console", :cyan
2519
+ say 'No tracks found', :yellow
2520
+ say ''
2521
+ say 'Tracks appear after you upload your app to Google Play Console', :cyan
2537
2522
  say "and sync with: mysigner sync android --package #{package_name}", :cyan
2538
2523
  return
2539
2524
  end
@@ -2559,17 +2544,17 @@ module Mysigner
2559
2544
  updated = Time.parse(track['updated_at']).strftime('%Y-%m-%d %H:%M')
2560
2545
  say " Updated: #{updated}"
2561
2546
  end
2562
- say ""
2547
+ say ''
2563
2548
  end
2564
2549
 
2565
2550
  say "Total: #{tracks.count} track(s)", :yellow
2566
2551
  rescue Mysigner::NotFoundError => e
2567
- if e.message.include?("Android app")
2552
+ if e.message.include?('Android app')
2568
2553
  error "Android app not found: #{package_name}"
2569
- say ""
2570
- say "💡 App not found:", :cyan
2571
- say " → Check the package name is correct", :yellow
2572
- say " → List your apps: mysigner apps --platform android", :yellow
2554
+ say ''
2555
+ say '💡 App not found:', :cyan
2556
+ say ' → Check the package name is correct', :yellow
2557
+ say ' → List your apps: mysigner apps --platform android', :yellow
2573
2558
  say " → Register the app: mysigner android add #{package_name}", :yellow
2574
2559
  else
2575
2560
  error "Not found: #{e.message}"
@@ -2581,33 +2566,33 @@ module Mysigner
2581
2566
  end
2582
2567
  end
2583
2568
 
2584
- desc "track PACKAGE_NAME TRACK_NAME", "Show details for a specific Google Play track"
2569
+ desc 'track PACKAGE_NAME TRACK_NAME', 'Show details for a specific Google Play track'
2585
2570
  def track(package_name = nil, track_name = nil)
2586
2571
  config = load_config
2587
2572
  client = create_client(config)
2588
2573
 
2589
2574
  if package_name.nil? || track_name.nil?
2590
- error "Usage: mysigner track PACKAGE_NAME TRACK_NAME"
2591
- say ""
2592
- say "Example: mysigner track com.example.myapp production", :yellow
2593
- say " mysigner track com.example.myapp beta", :yellow
2594
- say ""
2595
- say "Common track names: production, beta, alpha, internal", :cyan
2596
- say ""
2597
- say "💡 To see available tracks:", :cyan
2598
- say " mysigner tracks com.example.myapp", :cyan
2575
+ error 'Usage: mysigner track PACKAGE_NAME TRACK_NAME'
2576
+ say ''
2577
+ say 'Example: mysigner track com.example.myapp production', :yellow
2578
+ say ' mysigner track com.example.myapp beta', :yellow
2579
+ say ''
2580
+ say 'Common track names: production, beta, alpha, internal', :cyan
2581
+ say ''
2582
+ say '💡 To see available tracks:', :cyan
2583
+ say ' mysigner tracks com.example.myapp', :cyan
2599
2584
  exit 1
2600
2585
  end
2601
2586
 
2602
2587
  say "🎯 Track: #{track_name}", :cyan
2603
2588
  say " Package: #{package_name}", :white
2604
- say ""
2589
+ say ''
2605
2590
 
2606
2591
  begin
2607
2592
  response = client.get("/api/v1/organizations/#{config.current_organization_id}/android_apps/package/#{package_name}/tracks/#{track_name}")
2608
2593
  track = response[:data]
2609
2594
 
2610
- say "Details:", :bold
2595
+ say 'Details:', :bold
2611
2596
  say " Track Name: #{track['track_name']}"
2612
2597
  say " Status: #{track['status'] || 'unknown'}"
2613
2598
 
@@ -2617,9 +2602,9 @@ module Mysigner
2617
2602
  end
2618
2603
 
2619
2604
  # Show releases info
2605
+ say ''
2620
2606
  if track['releases'].is_a?(Array) && track['releases'].any?
2621
- say ""
2622
- say "Releases:", :bold
2607
+ say 'Releases:', :bold
2623
2608
  track['releases'].each_with_index do |release, idx|
2624
2609
  say " Release #{idx + 1}:", :white
2625
2610
  say " Status: #{release['status']}" if release['status']
@@ -2627,14 +2612,12 @@ module Mysigner
2627
2612
  version_codes = release['versionCodes'] || release['version_codes'] || []
2628
2613
  say " Version Codes: #{version_codes.join(', ')}" if version_codes.any?
2629
2614
 
2630
- if release['name']
2631
- say " Name: #{release['name']}"
2632
- end
2615
+ say " Name: #{release['name']}" if release['name']
2633
2616
 
2634
2617
  if release['releaseNotes'] || release['release_notes']
2635
2618
  notes = release['releaseNotes'] || release['release_notes']
2636
2619
  if notes.is_a?(Array) && notes.any?
2637
- say " Release Notes:"
2620
+ say ' Release Notes:'
2638
2621
  notes.each do |note|
2639
2622
  lang = note['language'] || 'en-US'
2640
2623
  text = note['text'] || ''
@@ -2649,24 +2632,22 @@ module Mysigner
2649
2632
  end
2650
2633
  end
2651
2634
  else
2652
- say ""
2653
- say "No releases found in this track", :yellow
2635
+ say 'No releases found in this track', :yellow
2654
2636
  end
2655
-
2656
2637
  rescue Mysigner::NotFoundError => e
2657
- if e.message.include?("Android app")
2638
+ if e.message.include?('Android app')
2658
2639
  error "Android app not found: #{package_name}"
2659
- say ""
2660
- say "💡 App not found:", :cyan
2661
- say " → Check the package name is correct", :yellow
2662
- say " → List your apps: mysigner apps --platform android", :yellow
2663
- elsif e.message.include?("Track")
2640
+ say ''
2641
+ say '💡 App not found:', :cyan
2642
+ say ' → Check the package name is correct', :yellow
2643
+ say ' → List your apps: mysigner apps --platform android', :yellow
2644
+ elsif e.message.include?('Track')
2664
2645
  error "Track not found: #{track_name}"
2665
- say ""
2666
- say "💡 Track not found:", :cyan
2667
- say " → Check the track name is correct", :yellow
2646
+ say ''
2647
+ say '💡 Track not found:', :cyan
2648
+ say ' → Check the track name is correct', :yellow
2668
2649
  say " → List available tracks: mysigner tracks #{package_name}", :yellow
2669
- say " → Common tracks: production, beta, alpha, internal", :yellow
2650
+ say ' → Common tracks: production, beta, alpha, internal', :yellow
2670
2651
  else
2671
2652
  error "Not found: #{e.message}"
2672
2653
  end
@@ -2679,7 +2660,7 @@ module Mysigner
2679
2660
 
2680
2661
  # ==================== APP GROUPS ====================
2681
2662
 
2682
- desc "app-groups", "List App Groups"
2663
+ desc 'app-groups', 'List App Groups'
2683
2664
  method_option :search, type: :string, aliases: '-q', desc: 'Search by identifier or name'
2684
2665
  method_option :page, type: :numeric, default: 1, desc: 'Page number'
2685
2666
  method_option :per_page, type: :numeric, default: 50, desc: 'Items per page'
@@ -2687,8 +2668,8 @@ module Mysigner
2687
2668
  config = load_config
2688
2669
  client = create_client(config)
2689
2670
 
2690
- say "📦 App Groups", :cyan
2691
- say ""
2671
+ say '📦 App Groups', :cyan
2672
+ say ''
2692
2673
 
2693
2674
  params = {
2694
2675
  page: options[:page],
@@ -2705,21 +2686,22 @@ module Mysigner
2705
2686
  pagination = response[:data]['pagination']
2706
2687
 
2707
2688
  if app_groups.empty?
2708
- say " No App Groups found", :yellow
2709
- say ""
2710
- say " Register one with: mysigner app-group register IDENTIFIER", :cyan
2711
- say ""
2712
- say " Note: App Groups must first be created in Apple Developer Portal", :yellow
2689
+ say ' No App Groups found', :yellow
2690
+ say ''
2691
+ say ' Register one with: mysigner app-group register IDENTIFIER', :cyan
2692
+ say ''
2693
+ say ' Note: App Groups must first be created in Apple Developer Portal', :yellow
2713
2694
  else
2714
2695
  app_groups.each do |g|
2715
2696
  say " • #{g['identifier']}", :green
2716
2697
  say " Name: #{g['name']}" if g['name'] && g['name'] != g['identifier']
2717
2698
  say " Team: #{g['team_id']}" if g['team_id']
2718
- say ""
2699
+ say ''
2719
2700
  end
2720
2701
 
2721
2702
  if pagination
2722
- say "Page #{pagination['page']} of #{pagination['total_pages']} (#{pagination['total']} total)", :yellow
2703
+ say "Page #{pagination['page']} of #{pagination['total_pages']} (#{pagination['total']} total)",
2704
+ :yellow
2723
2705
  end
2724
2706
  end
2725
2707
  rescue Mysigner::ClientError => e
@@ -2728,7 +2710,7 @@ module Mysigner
2728
2710
  end
2729
2711
  end
2730
2712
 
2731
- desc "app-group SUBCOMMAND", "Manage App Groups"
2713
+ desc 'app-group SUBCOMMAND', 'Manage App Groups'
2732
2714
  long_desc <<~DESC
2733
2715
  Register and delete App Groups.
2734
2716
 
@@ -2758,23 +2740,23 @@ module Mysigner
2758
2740
  case action
2759
2741
  when 'register'
2760
2742
  if identifier.nil?
2761
- error "Usage: mysigner app-group register IDENTIFIER [--name NAME]"
2762
- say ""
2763
- say "Example: mysigner app-group register group.com.company.shared", :yellow
2764
- say ""
2765
- say "Note: Create the App Group in Apple Developer Portal first!", :cyan
2743
+ error 'Usage: mysigner app-group register IDENTIFIER [--name NAME]'
2744
+ say ''
2745
+ say 'Example: mysigner app-group register group.com.company.shared', :yellow
2746
+ say ''
2747
+ say 'Note: Create the App Group in Apple Developer Portal first!', :cyan
2766
2748
  exit 1
2767
2749
  end
2768
2750
 
2769
2751
  unless identifier.start_with?('group.')
2770
2752
  error "App Group identifier must start with 'group.'"
2771
- say ""
2772
- say "Example: group.com.company.shared", :cyan
2753
+ say ''
2754
+ say 'Example: group.com.company.shared', :cyan
2773
2755
  exit 1
2774
2756
  end
2775
2757
 
2776
- say "📦 Registering App Group...", :cyan
2777
- say ""
2758
+ say '📦 Registering App Group...', :cyan
2759
+ say ''
2778
2760
 
2779
2761
  begin
2780
2762
  response = client.post(
@@ -2786,15 +2768,15 @@ module Mysigner
2786
2768
  )
2787
2769
 
2788
2770
  g = response[:data]['app_group'] || response[:data]
2789
- say "✓ App Group registered!", :green
2790
- say ""
2771
+ say '✓ App Group registered!', :green
2772
+ say ''
2791
2773
  say " Identifier: #{g['identifier'] || identifier}", :white
2792
2774
  say " Name: #{g['name']}", :white if g['name']
2793
- say ""
2794
- say " Remember: This only registers the App Group in My Signer.", :yellow
2795
- say " Make sure it exists in Apple Developer Portal.", :yellow
2775
+ say ''
2776
+ say ' Remember: This only registers the App Group in My Signer.', :yellow
2777
+ say ' Make sure it exists in Apple Developer Portal.', :yellow
2796
2778
  rescue Mysigner::ClientError => e
2797
- if e.message.include?("already exists")
2779
+ if e.message.include?('already exists')
2798
2780
  say "ℹ️ App Group already registered: #{identifier}", :yellow
2799
2781
  else
2800
2782
  error "Failed to register App Group: #{e.message}"
@@ -2804,12 +2786,12 @@ module Mysigner
2804
2786
 
2805
2787
  when 'delete'
2806
2788
  if identifier.nil?
2807
- error "Usage: mysigner app-group delete IDENTIFIER"
2789
+ error 'Usage: mysigner app-group delete IDENTIFIER'
2808
2790
  exit 1
2809
2791
  end
2810
2792
 
2811
- say "📦 Removing App Group...", :cyan
2812
- say ""
2793
+ say '📦 Removing App Group...', :cyan
2794
+ say ''
2813
2795
 
2814
2796
  begin
2815
2797
  # First find the app group by identifier
@@ -2830,9 +2812,9 @@ module Mysigner
2830
2812
  )
2831
2813
 
2832
2814
  say "✓ App Group removed from My Signer: #{identifier}", :green
2833
- say ""
2834
- say " Note: The App Group still exists in Apple Developer Portal.", :yellow
2835
- say " Delete it manually if needed.", :yellow
2815
+ say ''
2816
+ say ' Note: The App Group still exists in Apple Developer Portal.', :yellow
2817
+ say ' Delete it manually if needed.', :yellow
2836
2818
  rescue Mysigner::ClientError => e
2837
2819
  error "Failed to remove App Group: #{e.message}"
2838
2820
  exit 1
@@ -2840,17 +2822,17 @@ module Mysigner
2840
2822
 
2841
2823
  else
2842
2824
  error "Unknown action: #{action}"
2843
- say ""
2844
- say "Available actions:", :yellow
2845
- say " mysigner app-group register IDENTIFIER [--name NAME]", :cyan
2846
- say " mysigner app-group delete IDENTIFIER", :cyan
2825
+ say ''
2826
+ say 'Available actions:', :yellow
2827
+ say ' mysigner app-group register IDENTIFIER [--name NAME]', :cyan
2828
+ say ' mysigner app-group delete IDENTIFIER', :cyan
2847
2829
  exit 1
2848
2830
  end
2849
2831
  end
2850
2832
 
2851
2833
  # ==================== GOOGLE PLAY CREDENTIALS ====================
2852
2834
 
2853
- desc "gp-credential SUBCOMMAND", "Manage Google Play credentials (list, delete, activate, test)"
2835
+ desc 'gp-credential SUBCOMMAND', 'Manage Google Play credentials (list, delete, activate, test)'
2854
2836
  long_desc <<~DESC
2855
2837
  Manage Google Play API credentials for Android app distribution.
2856
2838
 
@@ -2888,17 +2870,17 @@ module Mysigner
2888
2870
 
2889
2871
  case action
2890
2872
  when 'list'
2891
- say "🔑 Google Play Credentials", :cyan
2892
- say ""
2873
+ say '🔑 Google Play Credentials', :cyan
2874
+ say ''
2893
2875
 
2894
2876
  begin
2895
2877
  response = client.get("/api/v1/organizations/#{config.current_organization_id}/google_play_credentials")
2896
2878
  credentials = response[:data]['google_play_credentials'] || []
2897
2879
 
2898
2880
  if credentials.empty?
2899
- say "No Google Play credentials found", :yellow
2900
- say ""
2901
- say "Set up credentials with: mysigner onboard", :yellow
2881
+ say 'No Google Play credentials found', :yellow
2882
+ say ''
2883
+ say 'Set up credentials with: mysigner onboard', :yellow
2902
2884
  return
2903
2885
  end
2904
2886
 
@@ -2917,7 +2899,7 @@ module Mysigner
2917
2899
  sync_color = cred['last_sync_status'] == 'success' ? :green : :red
2918
2900
  say " Sync Status: #{cred['last_sync_status']}", sync_color
2919
2901
  end
2920
- say ""
2902
+ say ''
2921
2903
  end
2922
2904
 
2923
2905
  say "Total: #{credentials.count} credential(s)", :yellow
@@ -2930,23 +2912,23 @@ module Mysigner
2930
2912
  credential_id = args[0]
2931
2913
 
2932
2914
  unless credential_id
2933
- error "Usage: mysigner gp-credential delete ID"
2934
- say ""
2915
+ error 'Usage: mysigner gp-credential delete ID'
2916
+ say ''
2935
2917
  say "Run 'mysigner gp-credential list' to see available IDs", :yellow
2936
2918
  exit 1
2937
2919
  end
2938
2920
 
2939
2921
  say "⚠️ You are about to delete Google Play credential ID: #{credential_id}", :yellow
2940
- say ""
2922
+ say ''
2941
2923
 
2942
- if yes?("Are you sure? This cannot be undone. (y/n)")
2924
+ if yes?('Are you sure? This cannot be undone. (y/n)')
2943
2925
  begin
2944
2926
  client.delete("/api/v1/organizations/#{config.current_organization_id}/google_play_credentials/#{credential_id}")
2945
- say ""
2946
- say "✓ Google Play credential deleted", :green
2927
+ say ''
2928
+ say '✓ Google Play credential deleted', :green
2947
2929
  rescue Mysigner::NotFoundError
2948
2930
  error "Credential not found with ID: #{credential_id}"
2949
- say ""
2931
+ say ''
2950
2932
  say "Run 'mysigner gp-credential list' to see available IDs", :yellow
2951
2933
  exit 1
2952
2934
  rescue Mysigner::ClientError => e
@@ -2954,30 +2936,30 @@ module Mysigner
2954
2936
  exit 1
2955
2937
  end
2956
2938
  else
2957
- say "Deletion cancelled", :yellow
2939
+ say 'Deletion cancelled', :yellow
2958
2940
  end
2959
2941
 
2960
2942
  when 'activate'
2961
2943
  credential_id = args[0]
2962
2944
 
2963
2945
  unless credential_id
2964
- error "Usage: mysigner gp-credential activate ID"
2965
- say ""
2946
+ error 'Usage: mysigner gp-credential activate ID'
2947
+ say ''
2966
2948
  say "Run 'mysigner gp-credential list' to see available IDs", :yellow
2967
2949
  exit 1
2968
2950
  end
2969
2951
 
2970
- say "🔑 Activating credential...", :cyan
2952
+ say '🔑 Activating credential...', :cyan
2971
2953
 
2972
2954
  begin
2973
2955
  response = client.post("/api/v1/organizations/#{config.current_organization_id}/google_play_credentials/#{credential_id}/activate")
2974
2956
  credential = response[:data]['google_play_credential'] || response[:data]
2975
- say "✓ Credential activated!", :green
2976
- say ""
2957
+ say '✓ Credential activated!', :green
2958
+ say ''
2977
2959
  say "#{credential['name']} is now the active Google Play credential", :cyan
2978
2960
  rescue Mysigner::NotFoundError
2979
2961
  error "Credential not found with ID: #{credential_id}"
2980
- say ""
2962
+ say ''
2981
2963
  say "Run 'mysigner gp-credential list' to see available IDs", :yellow
2982
2964
  exit 1
2983
2965
  rescue Mysigner::ClientError => e
@@ -2989,37 +2971,37 @@ module Mysigner
2989
2971
  credential_id = args[0]
2990
2972
 
2991
2973
  unless credential_id
2992
- error "Usage: mysigner gp-credential test ID"
2993
- say ""
2974
+ error 'Usage: mysigner gp-credential test ID'
2975
+ say ''
2994
2976
  say "Run 'mysigner gp-credential list' to see available IDs", :yellow
2995
2977
  exit 1
2996
2978
  end
2997
2979
 
2998
- say "🔑 Testing credential connection...", :cyan
2999
- say ""
2980
+ say '🔑 Testing credential connection...', :cyan
2981
+ say ''
3000
2982
 
3001
2983
  begin
3002
2984
  response = client.post("/api/v1/organizations/#{config.current_organization_id}/google_play_credentials/#{credential_id}/test")
3003
2985
  result = response[:data]
3004
2986
 
3005
2987
  if result['success']
3006
- say "✓ Connection successful!", :green
3007
- say ""
3008
- say " Google Play API is reachable with this credential", :cyan
2988
+ say '✓ Connection successful!', :green
2989
+ say ''
2990
+ say ' Google Play API is reachable with this credential', :cyan
3009
2991
  else
3010
- say "✗ Connection failed", :red
3011
- say ""
2992
+ say '✗ Connection failed', :red
2993
+ say ''
3012
2994
  say " Error: #{result['error']}", :red if result['error']
3013
- say ""
3014
- say "💡 Check that:", :cyan
3015
- say " → The service account JSON key is valid", :yellow
3016
- say " → The service account has Google Play Console access", :yellow
3017
- say " → API access is enabled in Google Play Console", :yellow
2995
+ say ''
2996
+ say '💡 Check that:', :cyan
2997
+ say ' → The service account JSON key is valid', :yellow
2998
+ say ' → The service account has Google Play Console access', :yellow
2999
+ say ' → API access is enabled in Google Play Console', :yellow
3018
3000
  exit 1
3019
3001
  end
3020
3002
  rescue Mysigner::NotFoundError
3021
3003
  error "Credential not found with ID: #{credential_id}"
3022
- say ""
3004
+ say ''
3023
3005
  say "Run 'mysigner gp-credential list' to see available IDs", :yellow
3024
3006
  exit 1
3025
3007
  rescue Mysigner::ClientError => e
@@ -3031,14 +3013,14 @@ module Mysigner
3031
3013
  invoke :help, ['gp-credential']
3032
3014
  else
3033
3015
  error "Unknown action: #{action}"
3034
- say "Available actions: list, delete, activate, test, help", :yellow
3016
+ say 'Available actions: list, delete, activate, test, help', :yellow
3035
3017
  exit 1
3036
3018
  end
3037
3019
  end
3038
3020
 
3039
3021
  # ==================== APP STORE RELEASES ====================
3040
3022
 
3041
- desc "release SUBCOMMAND", "Manage App Store release configurations (list, show, create, update)"
3023
+ desc 'release SUBCOMMAND', 'Manage App Store release configurations (list, show, create, update)'
3042
3024
  long_desc <<~DESC
3043
3025
  Manage App Store release configurations for iOS app distribution.
3044
3026
 
@@ -3107,8 +3089,8 @@ module Mysigner
3107
3089
 
3108
3090
  case action
3109
3091
  when 'list'
3110
- say "🚀 App Store Releases", :cyan
3111
- say ""
3092
+ say '🚀 App Store Releases', :cyan
3093
+ say ''
3112
3094
 
3113
3095
  params = {}
3114
3096
  params[:bundle_id] = options[:bundle_id] if options[:bundle_id]
@@ -3121,9 +3103,9 @@ module Mysigner
3121
3103
  releases = response[:data]['app_store_releases'] || []
3122
3104
 
3123
3105
  if releases.empty?
3124
- say "No release configurations found", :yellow
3125
- say ""
3126
- say "Create one with: mysigner release create --bundle-id-id ID", :yellow
3106
+ say 'No release configurations found', :yellow
3107
+ say ''
3108
+ say 'Create one with: mysigner release create --bundle-id-id ID', :yellow
3127
3109
  return
3128
3110
  end
3129
3111
 
@@ -3134,7 +3116,7 @@ module Mysigner
3134
3116
  say " Auto Submit: #{rel['auto_submit'] ? 'Yes' : 'No'}"
3135
3117
  say " Phased Release: #{rel['phased_release'] ? 'Yes' : 'No'}"
3136
3118
  say " Version: #{rel['version_string']}" if rel['version_string']
3137
- say ""
3119
+ say ''
3138
3120
  end
3139
3121
 
3140
3122
  say "Total: #{releases.count} release(s)", :yellow
@@ -3147,8 +3129,8 @@ module Mysigner
3147
3129
  release_id = args[0]
3148
3130
 
3149
3131
  unless release_id
3150
- error "Usage: mysigner release show ID"
3151
- say ""
3132
+ error 'Usage: mysigner release show ID'
3133
+ say ''
3152
3134
  say "Run 'mysigner release list' to see available IDs", :yellow
3153
3135
  exit 1
3154
3136
  end
@@ -3158,31 +3140,31 @@ module Mysigner
3158
3140
  rel = response[:data]['app_store_release'] || response[:data]
3159
3141
 
3160
3142
  say "🚀 Release Configuration (ID: #{rel['id']})", :cyan
3161
- say ""
3162
- say "Details:", :bold
3143
+ say ''
3144
+ say 'Details:', :bold
3163
3145
  say " App Name: #{rel['app_name'] || 'N/A'}"
3164
3146
  say " Bundle ID: #{rel['bundle_identifier'] || 'N/A'}"
3165
3147
  say " Version: #{rel['version_string'] || 'N/A'}"
3166
3148
  say " Release Type: #{rel['release_type'] || 'N/A'}"
3167
3149
  say " Auto Submit: #{rel['auto_submit'] ? 'Yes' : 'No'}"
3168
3150
  say " Phased Release: #{rel['phased_release'] ? 'Yes' : 'No'}"
3169
- say ""
3151
+ say ''
3170
3152
  if rel['whats_new'] && !rel['whats_new'].empty?
3171
3153
  say "What's New:", :bold
3172
3154
  say " #{rel['whats_new']}"
3173
- say ""
3155
+ say ''
3174
3156
  end
3175
- say "URLs:", :bold
3157
+ say 'URLs:', :bold
3176
3158
  say " Support: #{rel['support_url'] || 'N/A'}"
3177
3159
  say " Marketing: #{rel['marketing_url'] || 'N/A'}"
3178
3160
  say " Privacy: #{rel['privacy_url'] || 'N/A'}"
3179
3161
  if rel['scheduled_date']
3180
- say ""
3162
+ say ''
3181
3163
  say "Scheduled Date: #{rel['scheduled_date']}"
3182
3164
  end
3183
3165
  rescue Mysigner::NotFoundError
3184
3166
  error "Release not found with ID: #{release_id}"
3185
- say ""
3167
+ say ''
3186
3168
  say "Run 'mysigner release list' to see available IDs", :yellow
3187
3169
  exit 1
3188
3170
  rescue Mysigner::ClientError => e
@@ -3191,8 +3173,8 @@ module Mysigner
3191
3173
  end
3192
3174
 
3193
3175
  when 'create'
3194
- say "🚀 Creating release configuration...", :cyan
3195
- say ""
3176
+ say '🚀 Creating release configuration...', :cyan
3177
+ say ''
3196
3178
 
3197
3179
  body = {}
3198
3180
  body[:bundle_id_id] = options[:bundle_id_id] if options[:bundle_id_id]
@@ -3212,9 +3194,9 @@ module Mysigner
3212
3194
  )
3213
3195
  rel = response[:data]['app_store_release'] || response[:data]
3214
3196
 
3215
- say "✓ Release configuration created!", :green
3216
- say ""
3217
- say "Details:", :bold
3197
+ say '✓ Release configuration created!', :green
3198
+ say ''
3199
+ say 'Details:', :bold
3218
3200
  say " ID: #{rel['id']}"
3219
3201
  say " Bundle ID: #{rel['bundle_identifier'] || 'N/A'}"
3220
3202
  say " Release Type: #{rel['release_type'] || 'N/A'}"
@@ -3222,24 +3204,22 @@ module Mysigner
3222
3204
  say " Phased Release: #{rel['phased_release'] ? 'Yes' : 'No'}"
3223
3205
  rescue Mysigner::ValidationError => e
3224
3206
  if e.message.include?('already exists') || (e.error_code && e.error_code.to_s == '409')
3225
- error "Release configuration already exists for this bundle ID"
3226
- say ""
3207
+ error 'Release configuration already exists for this bundle ID'
3208
+ say ''
3227
3209
  say "💡 Use 'mysigner release update ID' to modify it", :cyan
3228
3210
  say " Run 'mysigner release list' to find the ID", :yellow
3229
3211
  else
3230
3212
  error "Validation failed: #{e.message}"
3231
- if e.details
3232
- e.details.each do |field, errors|
3233
- errors_text = errors.is_a?(Array) ? errors.join(', ') : errors.to_s
3234
- say " #{field}: #{errors_text}", :red
3235
- end
3213
+ e.details&.each do |field, errors|
3214
+ errors_text = errors.is_a?(Array) ? errors.join(', ') : errors.to_s
3215
+ say " #{field}: #{errors_text}", :red
3236
3216
  end
3237
3217
  end
3238
3218
  exit 1
3239
3219
  rescue Mysigner::ClientError => e
3240
3220
  if e.message.include?('409') || e.message.include?('already exists')
3241
- error "Release configuration already exists for this bundle ID"
3242
- say ""
3221
+ error 'Release configuration already exists for this bundle ID'
3222
+ say ''
3243
3223
  say "💡 Use 'mysigner release update ID' to modify it", :cyan
3244
3224
  say " Run 'mysigner release list' to find the ID", :yellow
3245
3225
  else
@@ -3252,8 +3232,8 @@ module Mysigner
3252
3232
  release_id = args[0]
3253
3233
 
3254
3234
  unless release_id
3255
- error "Usage: mysigner release update ID [OPTIONS]"
3256
- say ""
3235
+ error 'Usage: mysigner release update ID [OPTIONS]'
3236
+ say ''
3257
3237
  say "Run 'mysigner release list' to see available IDs", :yellow
3258
3238
  exit 1
3259
3239
  end
@@ -3268,8 +3248,8 @@ module Mysigner
3268
3248
  body[:release_type] = options[:release_type] if options[:release_type]
3269
3249
  body[:scheduled_date] = options[:scheduled_date] if options[:scheduled_date]
3270
3250
 
3271
- say "🚀 Updating release configuration...", :cyan
3272
- say ""
3251
+ say '🚀 Updating release configuration...', :cyan
3252
+ say ''
3273
3253
 
3274
3254
  begin
3275
3255
  response = client.patch(
@@ -3278,9 +3258,9 @@ module Mysigner
3278
3258
  )
3279
3259
  rel = response[:data]['app_store_release'] || response[:data]
3280
3260
 
3281
- say "✓ Release configuration updated!", :green
3282
- say ""
3283
- say "Details:", :bold
3261
+ say '✓ Release configuration updated!', :green
3262
+ say ''
3263
+ say 'Details:', :bold
3284
3264
  say " ID: #{rel['id']}"
3285
3265
  say " Bundle ID: #{rel['bundle_identifier'] || 'N/A'}"
3286
3266
  say " Release Type: #{rel['release_type'] || 'N/A'}"
@@ -3288,16 +3268,14 @@ module Mysigner
3288
3268
  say " Phased Release: #{rel['phased_release'] ? 'Yes' : 'No'}"
3289
3269
  rescue Mysigner::NotFoundError
3290
3270
  error "Release not found with ID: #{release_id}"
3291
- say ""
3271
+ say ''
3292
3272
  say "Run 'mysigner release list' to see available IDs", :yellow
3293
3273
  exit 1
3294
3274
  rescue Mysigner::ValidationError => e
3295
3275
  error "Validation failed: #{e.message}"
3296
- if e.details
3297
- e.details.each do |field, errors|
3298
- errors_text = errors.is_a?(Array) ? errors.join(', ') : errors.to_s
3299
- say " #{field}: #{errors_text}", :red
3300
- end
3276
+ e.details&.each do |field, errors|
3277
+ errors_text = errors.is_a?(Array) ? errors.join(', ') : errors.to_s
3278
+ say " #{field}: #{errors_text}", :red
3301
3279
  end
3302
3280
  exit 1
3303
3281
  rescue Mysigner::ClientError => e
@@ -3309,7 +3287,7 @@ module Mysigner
3309
3287
  invoke :help, ['release']
3310
3288
  else
3311
3289
  error "Unknown action: #{action}"
3312
- say "Available actions: list, show, create, update, help", :yellow
3290
+ say 'Available actions: list, show, create, update, help', :yellow
3313
3291
  exit 1
3314
3292
  end
3315
3293
  end