mysigner 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.githooks/pre-commit +15 -0
  3. data/.githooks/pre-push +21 -0
  4. data/.github/workflows/ci.yml +29 -0
  5. data/.gitignore +1 -0
  6. data/.rubocop.yml +55 -0
  7. data/.rubocop_todo.yml +112 -0
  8. data/CHANGELOG.md +96 -0
  9. data/Gemfile +5 -3
  10. data/Gemfile.lock +38 -8
  11. data/README.md +87 -17
  12. data/Rakefile +5 -3
  13. data/bin/console +4 -3
  14. data/bin/setup +3 -0
  15. data/exe/mysigner +2 -1
  16. data/lib/mysigner/build/android_executor.rb +46 -52
  17. data/lib/mysigner/build/android_parser.rb +33 -40
  18. data/lib/mysigner/build/configurator.rb +17 -16
  19. data/lib/mysigner/build/detector.rb +39 -50
  20. data/lib/mysigner/build/error_analyzer.rb +70 -68
  21. data/lib/mysigner/build/executor.rb +30 -37
  22. data/lib/mysigner/build/parser.rb +18 -18
  23. data/lib/mysigner/cli/auth_commands.rb +735 -752
  24. data/lib/mysigner/cli/build_commands.rb +697 -721
  25. data/lib/mysigner/cli/concerns/actionable_suggestions.rb +208 -154
  26. data/lib/mysigner/cli/concerns/api_helpers.rb +46 -54
  27. data/lib/mysigner/cli/concerns/error_handlers.rb +247 -237
  28. data/lib/mysigner/cli/concerns/helpers.rb +12 -1
  29. data/lib/mysigner/cli/diagnostic_commands.rb +659 -635
  30. data/lib/mysigner/cli/resource_commands.rb +1266 -822
  31. data/lib/mysigner/cli/validate_commands.rb +161 -0
  32. data/lib/mysigner/cli.rb +5 -1
  33. data/lib/mysigner/client.rb +27 -19
  34. data/lib/mysigner/config.rb +93 -56
  35. data/lib/mysigner/export/exporter.rb +32 -36
  36. data/lib/mysigner/signing/certificate_checker.rb +18 -23
  37. data/lib/mysigner/signing/keystore_manager.rb +34 -39
  38. data/lib/mysigner/signing/validator.rb +38 -40
  39. data/lib/mysigner/signing/wizard.rb +329 -342
  40. data/lib/mysigner/upload/app_store_automation.rb +51 -49
  41. data/lib/mysigner/upload/app_store_submission.rb +87 -92
  42. data/lib/mysigner/upload/play_store_uploader.rb +98 -115
  43. data/lib/mysigner/upload/uploader.rb +101 -109
  44. data/lib/mysigner/version.rb +3 -1
  45. data/lib/mysigner.rb +13 -11
  46. data/mysigner.gemspec +36 -33
  47. data/test_manual.rb +37 -36
  48. metadata +38 -16
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Mysigner
2
4
  class CLI < Thor
3
5
  module Concerns
@@ -14,12 +16,12 @@ module Mysigner
14
16
  /no.*processed.*build/i,
15
17
  /waiting.*for.*build/i
16
18
  ],
17
- title: "Build Not Found",
19
+ title: 'Build Not Found',
18
20
  suggestions: [
19
- "Upload a build first: mysigner ship testflight",
20
- "If already uploaded, wait 5-15 minutes for Apple to process it",
21
- "Use --wait flag to poll for processing: mysigner ship appstore --wait",
22
- "Sync your builds: mysigner sync ios"
21
+ 'Upload a build first: mysigner ship testflight',
22
+ 'If already uploaded, wait 5-15 minutes for Apple to process it',
23
+ 'Use --wait flag to poll for processing: mysigner ship appstore --wait',
24
+ 'Sync your builds: mysigner sync ios'
23
25
  ]
24
26
  },
25
27
 
@@ -30,12 +32,12 @@ module Mysigner
30
32
  /processing.*complete/i,
31
33
  /not.*ready/i
32
34
  ],
33
- title: "Build Still Processing",
35
+ title: 'Build Still Processing',
34
36
  suggestions: [
35
- "Apple typically takes 5-15 minutes to process builds",
36
- "Use --wait flag to automatically wait: mysigner ship appstore --wait",
37
- "Check App Store Connect for processing status",
38
- "Increase timeout: mysigner ship appstore --wait --asc-timeout-seconds 1800"
37
+ 'Apple typically takes 5-15 minutes to process builds',
38
+ 'Use --wait flag to automatically wait: mysigner ship appstore --wait',
39
+ 'Check App Store Connect for processing status',
40
+ 'Increase timeout: mysigner ship appstore --wait --asc-timeout-seconds 1800'
39
41
  ]
40
42
  },
41
43
 
@@ -46,12 +48,12 @@ module Mysigner
46
48
  /provisioning.*expired/i,
47
49
  /certificate.*expired/i
48
50
  ],
49
- title: "Expired Profile or Certificate",
51
+ title: 'Expired Profile or Certificate',
50
52
  suggestions: [
51
- "List your profiles: mysigner profiles",
52
- "Check expiration dates in My Signer dashboard",
53
- "Regenerate expired profiles in Apple Developer Portal",
54
- "Download fresh profile: mysigner profile download <ID>"
53
+ 'List your profiles: mysigner profiles',
54
+ 'Check expiration dates in My Signer dashboard',
55
+ 'Regenerate expired profiles in Apple Developer Portal',
56
+ 'Download fresh profile: mysigner profile download <ID>'
55
57
  ]
56
58
  },
57
59
 
@@ -62,12 +64,12 @@ module Mysigner
62
64
  /missing.*profile/i,
63
65
  /unable.*find.*profile/i
64
66
  ],
65
- title: "Provisioning Profile Not Found",
67
+ title: 'Provisioning Profile Not Found',
66
68
  suggestions: [
67
- "List available profiles: mysigner profiles",
68
- "Sync profiles from Apple: mysigner sync ios",
69
- "Create profile in Apple Developer Portal",
70
- "Check if profile is for correct Bundle ID"
69
+ 'List available profiles: mysigner profiles',
70
+ 'Sync profiles from Apple: mysigner sync ios',
71
+ 'Create profile in Apple Developer Portal',
72
+ 'Check if profile is for correct Bundle ID'
71
73
  ]
72
74
  },
73
75
 
@@ -78,11 +80,11 @@ module Mysigner
78
80
  /no.*signing.*certificate/i,
79
81
  /missing.*certificate/i
80
82
  ],
81
- title: "Signing Certificate Not Found",
83
+ title: 'Signing Certificate Not Found',
82
84
  suggestions: [
83
- "List certificates: mysigner certificates",
84
- "Download and install: mysigner certificate download <ID>",
85
- "Check Keychain Access for installed certificates",
85
+ 'List certificates: mysigner certificates',
86
+ 'Download and install: mysigner certificate download <ID>',
87
+ 'Check Keychain Access for installed certificates',
86
88
  "Run 'mysigner doctor' to diagnose signing issues"
87
89
  ]
88
90
  },
@@ -94,12 +96,12 @@ module Mysigner
94
96
  /bundle.*identifier.*not.*match/i,
95
97
  /app.*id.*not.*found/i
96
98
  ],
97
- title: "Bundle ID Issue",
99
+ title: 'Bundle ID Issue',
98
100
  suggestions: [
99
- "Verify Bundle ID in Xcode matches Apple Developer Portal",
100
- "List Bundle IDs: mysigner bundleids",
101
- "Register new Bundle ID: mysigner bundleids create <ID>",
102
- "Check My Signer dashboard for registered apps"
101
+ 'Verify Bundle ID in Xcode matches Apple Developer Portal',
102
+ 'List Bundle IDs: mysigner bundleid list',
103
+ 'Register new Bundle ID: mysigner bundleid register <ID>',
104
+ 'Check My Signer dashboard for registered apps'
103
105
  ]
104
106
  },
105
107
 
@@ -110,12 +112,12 @@ module Mysigner
110
112
  /app.*not.*found/i,
111
113
  /unable.*find.*app/i
112
114
  ],
113
- title: "App Not Found",
115
+ title: 'App Not Found',
114
116
  suggestions: [
115
- "Ensure app exists in App Store Connect",
116
- "Create app in App Store Connect first",
117
- "Verify Bundle ID matches: check your Xcode project",
118
- "Sync from App Store Connect: mysigner sync ios"
117
+ 'Ensure app exists in App Store Connect',
118
+ 'Create app in App Store Connect first',
119
+ 'Verify Bundle ID matches: check your Xcode project',
120
+ 'Sync from App Store Connect: mysigner sync ios'
119
121
  ]
120
122
  },
121
123
 
@@ -125,11 +127,11 @@ module Mysigner
125
127
  /version.*already.*exists/i,
126
128
  /duplicate.*version/i
127
129
  ],
128
- title: "Version Already Exists",
130
+ title: 'Version Already Exists',
129
131
  suggestions: [
130
- "Increment version in Xcode (CFBundleShortVersionString)",
131
- "Or increment build number (CFBundleVersion)",
132
- "Check existing versions in App Store Connect"
132
+ 'Increment version in Xcode (CFBundleShortVersionString)',
133
+ 'Or increment build number (CFBundleVersion)',
134
+ 'Check existing versions in App Store Connect'
133
135
  ]
134
136
  },
135
137
 
@@ -141,12 +143,12 @@ module Mysigner
141
143
  /support.*url.*required/i,
142
144
  /cannot.*submit.*missing/i
143
145
  ],
144
- title: "Missing App Store Metadata",
146
+ title: 'Missing App Store Metadata',
145
147
  suggestions: [
146
- "Configure release in My Signer dashboard",
148
+ 'Configure release in My Signer dashboard',
147
149
  "Provide What's New via CLI: --whats-new \"Your text\"",
148
- "Ensure support URL is set in App Store Connect",
149
- "Complete app information in App Store Connect"
150
+ 'Ensure support URL is set in App Store Connect',
151
+ 'Complete app information in App Store Connect'
150
152
  ]
151
153
  },
152
154
 
@@ -157,12 +159,12 @@ module Mysigner
157
159
  /no.*xcarchive/i,
158
160
  /.xcarchive.*not.*found/i
159
161
  ],
160
- title: "Archive Not Found",
162
+ title: 'Archive Not Found',
161
163
  suggestions: [
162
- "Build your app first: mysigner build",
163
- "Or use: mysigner ship testflight (handles build automatically)",
164
- "Check if archive path is correct",
165
- "Verify Xcode build succeeded"
164
+ 'Build your app first: mysigner build',
165
+ 'Or use: mysigner ship testflight (handles build automatically)',
166
+ 'Check if archive path is correct',
167
+ 'Verify Xcode build succeeded'
166
168
  ]
167
169
  },
168
170
 
@@ -171,12 +173,12 @@ module Mysigner
171
173
  /ipa.*not.*found/i,
172
174
  /no.*ipa.*file/i
173
175
  ],
174
- title: "IPA File Not Found",
176
+ title: 'IPA File Not Found',
175
177
  suggestions: [
176
- "Export IPA from archive: mysigner export <archive_path>",
177
- "Or use: mysigner ship testflight (handles export automatically)",
178
- "Check if export succeeded",
179
- "Verify export method matches profile type"
178
+ 'Export IPA from archive: mysigner export <archive_path>',
179
+ 'Or use: mysigner ship testflight (handles export automatically)',
180
+ 'Check if export succeeded',
181
+ 'Verify export method matches profile type'
180
182
  ]
181
183
  }
182
184
  }.freeze
@@ -190,12 +192,12 @@ module Mysigner
190
192
  /no.*keystore/i,
191
193
  /missing.*keystore/i
192
194
  ],
193
- title: "Keystore Not Found",
195
+ title: 'Keystore Not Found',
194
196
  suggestions: [
195
- "Upload keystore: mysigner keystore upload <path>",
196
- "List keystores: mysigner keystores",
197
- "Download keystore: mysigner keystore download <ID>",
198
- "Check keystore path in build.gradle"
197
+ 'Upload keystore: mysigner keystore upload <path>',
198
+ 'List keystores: mysigner keystore list',
199
+ 'Download keystore: mysigner keystore download <ID>',
200
+ 'Check keystore path in build.gradle'
199
201
  ]
200
202
  },
201
203
 
@@ -205,11 +207,11 @@ module Mysigner
205
207
  /wrong.*password/i,
206
208
  /incorrect.*password/i
207
209
  ],
208
- title: "Keystore Password Issue",
210
+ title: 'Keystore Password Issue',
209
211
  suggestions: [
210
- "Verify keystore password is correct",
211
- "Check password in My Signer dashboard",
212
- "Update keystore password: mysigner keystore update <ID>"
212
+ 'Verify keystore password is correct',
213
+ 'Check password in My Signer dashboard',
214
+ 'Update keystore password: mysigner keystore update <ID>'
213
215
  ]
214
216
  },
215
217
 
@@ -219,14 +221,14 @@ module Mysigner
219
221
  /package.*not.*found/i,
220
222
  /first.*build.*uploaded.*manually/i
221
223
  ],
222
- title: "First Upload Required",
224
+ title: 'First Upload Required',
223
225
  suggestions: [
224
- "Google Play requires the FIRST build to be uploaded manually:",
225
- " 1. Build AAB: mysigner android build",
226
- " 2. Go to Play Console → Your App → Internal testing",
226
+ 'Google Play requires the FIRST build to be uploaded manually:',
227
+ ' 1. Build AAB: mysigner android build',
228
+ ' 2. Go to Play Console → Your App → Internal testing',
227
229
  " 3. Click 'Create release' and upload the AAB",
228
- " 4. Save the release (no need to roll out)",
229
- "After that, mysigner ship will work for future uploads"
230
+ ' 4. Save the release (no need to roll out)',
231
+ 'After that, mysigner ship will work for future uploads'
230
232
  ]
231
233
  },
232
234
 
@@ -237,11 +239,11 @@ module Mysigner
237
239
  /already.*used/i,
238
240
  /duplicate.*version.*code/i
239
241
  ],
240
- title: "Version Code Conflict",
242
+ title: 'Version Code Conflict',
241
243
  suggestions: [
242
- "Version code already exists on Google Play",
243
- "Run the command again - mysigner auto-increments the version",
244
- "Or manually increment versionCode in build.gradle"
244
+ 'Version code already exists on Google Play',
245
+ 'Run the command again - mysigner auto-increments the version',
246
+ 'Or manually increment versionCode in build.gradle'
245
247
  ]
246
248
  },
247
249
 
@@ -252,13 +254,13 @@ module Mysigner
252
254
  /service.*account.*json.*not.*found/i,
253
255
  /no.*credentials/i
254
256
  ],
255
- title: "Service Account Not Found",
257
+ title: 'Service Account Not Found',
256
258
  suggestions: [
257
- "Set up Google Play credentials in My Signer dashboard:",
258
- " 1. Go to Play Console → API access → Service accounts",
259
- " 2. Create a service account with Editor access",
260
- " 3. Download the JSON key",
261
- " 4. Upload to My Signer dashboard → Google Play Settings"
259
+ 'Set up Google Play credentials in My Signer dashboard:',
260
+ ' 1. Go to Play Console → API access → Service accounts',
261
+ ' 2. Create a service account with Editor access',
262
+ ' 3. Download the JSON key',
263
+ ' 4. Upload to My Signer dashboard → Google Play Settings'
262
264
  ]
263
265
  },
264
266
 
@@ -269,14 +271,14 @@ module Mysigner
269
271
  /forbidden/i,
270
272
  /access.*denied/i
271
273
  ],
272
- title: "Service Account Permission Denied",
274
+ title: 'Service Account Permission Denied',
273
275
  suggestions: [
274
- "Service account lacks required permissions",
275
- "In Play Console → API access:",
276
- " 1. Find your service account",
276
+ 'Service account lacks required permissions',
277
+ 'In Play Console → API access:',
278
+ ' 1. Find your service account',
277
279
  " 2. Click 'Manage Play Console permissions'",
278
280
  " 3. Grant 'Admin' or 'Release manager' access to the app",
279
- "Note: Permission changes take ~15 minutes to propagate"
281
+ 'Note: Permission changes take ~15 minutes to propagate'
280
282
  ]
281
283
  },
282
284
 
@@ -287,13 +289,13 @@ module Mysigner
287
289
  /precondition.*check.*failed/i,
288
290
  /track.*not.*ready/i
289
291
  ],
290
- title: "Track Not Set Up in Play Console",
292
+ title: 'Track Not Set Up in Play Console',
291
293
  suggestions: [
292
- "Complete track setup in Google Play Console:",
293
- " For PRODUCTION: Complete store listing, content rating, pricing",
294
- " For BETA/ALPHA: Create testing track and add testers",
295
- " For INTERNAL: Add internal testers",
296
- "Your AAB was uploaded - go to Play Console to finish setup"
294
+ 'Complete track setup in Google Play Console:',
295
+ ' For PRODUCTION: Complete store listing, content rating, pricing',
296
+ ' For BETA/ALPHA: Create testing track and add testers',
297
+ ' For INTERNAL: Add internal testers',
298
+ 'Your AAB was uploaded - go to Play Console to finish setup'
297
299
  ]
298
300
  },
299
301
 
@@ -304,12 +306,12 @@ module Mysigner
304
306
  /no.*aab.*file/i,
305
307
  /app.*bundle.*not.*found/i
306
308
  ],
307
- title: "AAB File Not Found",
309
+ title: 'AAB File Not Found',
308
310
  suggestions: [
309
- "Build your Android app first: mysigner android build",
310
- "Or use: mysigner ship internal (handles build automatically)",
311
- "Check if Gradle build succeeded",
312
- "Verify AAB path in build output"
311
+ 'Build your Android app first: mysigner android build',
312
+ 'Or use: mysigner ship internal (handles build automatically)',
313
+ 'Check if Gradle build succeeded',
314
+ 'Verify AAB path in build output'
313
315
  ]
314
316
  },
315
317
 
@@ -320,28 +322,72 @@ module Mysigner
320
322
  /wrong.*key/i,
321
323
  /incorrect.*signature/i
322
324
  ],
323
- title: "Signing Key Mismatch",
325
+ title: 'Signing Key Mismatch',
324
326
  suggestions: [
325
- "AAB is signed with a different key than expected",
327
+ 'AAB is signed with a different key than expected',
326
328
  "Verify keystore matches what's registered in Play Console",
327
- "Check active keystore: mysigner keystores --active",
328
- "If using Google Play App Signing, upload the correct upload key"
329
+ 'Check active keystore: mysigner keystore list --active',
330
+ 'If using Google Play App Signing, upload the correct upload key'
329
331
  ]
330
332
  }
331
333
  }.freeze
332
334
 
333
335
  # API/Connection error patterns
334
336
  API_ERROR_PATTERNS = {
337
+ plan_upgrade_required: {
338
+ patterns: [
339
+ /plan_upgrade_required/i,
340
+ /upgrade.*required/i,
341
+ /requires?.*(free|pro|team|paid)/i
342
+ ],
343
+ title: 'Plan Upgrade Required',
344
+ suggestions: [
345
+ 'This feature is blocked by your current plan',
346
+ 'Upgrade from the My Signer pricing page or dashboard to continue',
347
+ "Use 'mysigner switch' if you need to change to a different organization first"
348
+ ]
349
+ },
350
+
351
+ quota_exhausted: {
352
+ patterns: [
353
+ /quota_exhausted/i,
354
+ /quota.*exceeded/i,
355
+ /limit.*reached/i,
356
+ /storage.*limit/i,
357
+ /upload.*limit/i
358
+ ],
359
+ title: 'Plan Limit Reached',
360
+ suggestions: [
361
+ 'Your organization has reached a plan limit for this feature',
362
+ 'Free up capacity or upgrade your plan in the My Signer dashboard',
363
+ 'Check the pricing page for the next plan tier and included limits'
364
+ ]
365
+ },
366
+
367
+ seat_limit_reached: {
368
+ patterns: [
369
+ /seat.*limit/i,
370
+ /invite.*limit/i,
371
+ /member.*limit/i
372
+ ],
373
+ title: 'Seat Limit Reached',
374
+ suggestions: [
375
+ 'Your organization has reached its member or invite limit',
376
+ 'Remove unused members or invitations, or upgrade to a plan with more seats',
377
+ "Use 'mysigner orgs' or the dashboard to confirm you are in the correct organization"
378
+ ]
379
+ },
380
+
335
381
  rate_limited: {
336
382
  patterns: [
337
383
  /rate.*limit/i,
338
384
  /too.*many.*requests/i,
339
385
  /429/
340
386
  ],
341
- title: "Rate Limited",
387
+ title: 'Rate Limited',
342
388
  suggestions: [
343
- "Too many API requests - wait a moment and try again",
344
- "If problem persists, check API status"
389
+ 'Too many API requests - wait a moment and try again',
390
+ 'If problem persists, check API status'
345
391
  ]
346
392
  },
347
393
 
@@ -351,11 +397,11 @@ module Mysigner
351
397
  /internal.*error/i,
352
398
  /500|502|503|504/
353
399
  ],
354
- title: "Server Error",
400
+ title: 'Server Error',
355
401
  suggestions: [
356
- "My Signer server encountered an error",
357
- "Try again in a few moments",
358
- "If problem persists, check service status or contact support"
402
+ 'My Signer server encountered an error',
403
+ 'Try again in a few moments',
404
+ 'If problem persists, check service status or contact support'
359
405
  ]
360
406
  },
361
407
 
@@ -364,11 +410,11 @@ module Mysigner
364
410
  /timeout/i,
365
411
  /timed.*out/i
366
412
  ],
367
- title: "Request Timeout",
413
+ title: 'Request Timeout',
368
414
  suggestions: [
369
- "Request took too long to complete",
370
- "Check your network connection",
371
- "Try again - this may be temporary"
415
+ 'Request took too long to complete',
416
+ 'Check your network connection',
417
+ 'Try again - this may be temporary'
372
418
  ]
373
419
  }
374
420
  }.freeze
@@ -384,10 +430,8 @@ module Mysigner
384
430
  IOS_ERROR_PATTERNS.merge(ANDROID_ERROR_PATTERNS).merge(API_ERROR_PATTERNS)
385
431
  end
386
432
 
387
- patterns.each do |_key, error_info|
388
- if error_info[:patterns].any? { |pattern| error_message =~ pattern }
389
- return error_info
390
- end
433
+ patterns.each_value do |error_info|
434
+ return error_info if error_info[:patterns].any? { |pattern| error_message =~ pattern }
391
435
  end
392
436
 
393
437
  nil
@@ -398,33 +442,33 @@ module Mysigner
398
442
  return nil unless error_info
399
443
 
400
444
  lines = []
401
- lines << ""
402
- lines << "=" * 70
445
+ lines << ''
446
+ lines << ('=' * 70)
403
447
  lines << " 💡 #{error_info[:title]}: How to fix"
404
- lines << "=" * 70
405
- lines << ""
448
+ lines << ('=' * 70)
449
+ lines << ''
406
450
 
407
451
  error_info[:suggestions].each do |suggestion|
408
452
  # Preserve indentation for multi-line suggestions
409
- if suggestion.start_with?(" ")
410
- lines << " #{suggestion}"
411
- else
412
- lines << " → #{suggestion}"
413
- end
453
+ lines << if suggestion.start_with?(' ')
454
+ " #{suggestion}"
455
+ else
456
+ " → #{suggestion}"
457
+ end
414
458
  end
415
459
 
416
- lines << ""
417
- lines << " 📚 More help:"
460
+ lines << ''
461
+ lines << ' 📚 More help:'
418
462
  lines << " • Run 'mysigner doctor' to check your setup"
419
463
  lines << " • Run 'mysigner help <command>' for command options"
420
- lines << ""
464
+ lines << ''
421
465
 
422
466
  lines.join("\n")
423
467
  end
424
468
 
425
469
  # Display actionable suggestions for an error (helper for CLI commands)
426
- def show_actionable_suggestions(error_message, platform: nil)
427
- error_info = find_suggestions_for_error(error_message.to_s, platform: platform)
470
+ def show_actionable_suggestions(error_or_message, platform: nil)
471
+ error_info = find_suggestions_for_error(actionable_error_lookup_text(error_or_message), platform: platform)
428
472
  return false unless error_info
429
473
 
430
474
  output = format_actionable_suggestions(error_info)
@@ -434,64 +478,74 @@ module Mysigner
434
478
 
435
479
  # Enhanced error display that includes suggestions
436
480
  def display_error_with_suggestions(error, platform: nil, context: {})
437
- say ""
438
- say "=" * 80, :red
481
+ say ''
482
+ say '=' * 80, :red
439
483
  say "✗ #{context[:title] || 'Error'}", :red
440
- say "=" * 80, :red
441
- say ""
484
+ say '=' * 80, :red
485
+ say ''
442
486
  say "Error: #{error.message}", :red
443
- say ""
487
+ say ''
488
+
489
+ if error.respond_to?(:suggestion) && error.suggestion
490
+ say "Suggestion: #{error.suggestion}", :yellow
491
+ say ''
492
+ end
444
493
 
445
494
  # Show actionable suggestions if available
446
- show_actionable_suggestions(error.message, platform: platform)
495
+ show_actionable_suggestions(error, platform: platform)
447
496
 
448
497
  # Show additional context
449
- if context[:archive_path] && File.exist?(context[:archive_path])
450
- say "Archive saved at: #{context[:archive_path]}", :yellow
451
- end
498
+ say "Archive saved at: #{context[:archive_path]}", :yellow if context[:archive_path] && File.exist?(context[:archive_path])
452
499
 
453
- if context[:ipa_path] && File.exist?(context[:ipa_path])
454
- say "IPA saved at: #{context[:ipa_path]}", :yellow
455
- end
500
+ say "IPA saved at: #{context[:ipa_path]}", :yellow if context[:ipa_path] && File.exist?(context[:ipa_path])
456
501
 
457
- if context[:aab_path] && File.exist?(context[:aab_path])
458
- say "AAB saved at: #{context[:aab_path]}", :yellow
459
- end
502
+ say "AAB saved at: #{context[:aab_path]}", :yellow if context[:aab_path] && File.exist?(context[:aab_path])
460
503
 
461
504
  # Debug info
462
505
  if ENV['DEBUG']
463
- say ""
464
- say "Debug info:", :yellow
506
+ say ''
507
+ say 'Debug info:', :yellow
465
508
  say " Error class: #{error.class}", :yellow
466
- say " Backtrace:", :yellow
509
+ say ' Backtrace:', :yellow
467
510
  error.backtrace&.first(5)&.each do |line|
468
511
  say " #{line}", :yellow
469
512
  end
470
513
  else
471
- say "💡 For more details, run with DEBUG=1", :yellow
514
+ say '💡 For more details, run with DEBUG=1', :yellow
472
515
  end
473
516
 
474
- say ""
517
+ say ''
518
+ end
519
+
520
+ def actionable_error_lookup_text(error_or_message)
521
+ return error_or_message.to_s unless error_or_message.respond_to?(:message)
522
+
523
+ [
524
+ (error_or_message.error_code if error_or_message.respond_to?(:error_code)),
525
+ error_or_message.message,
526
+ (error_or_message.suggestion if error_or_message.respond_to?(:suggestion))
527
+ ].compact.join(' ')
475
528
  end
476
529
 
477
530
  # Quick reference suggestions for common operations
478
531
  QUICK_REFERENCE = {
479
532
  sync: {
480
- ios: "mysigner sync ios",
481
- android: "mysigner sync android"
533
+ ios: 'mysigner sync ios',
534
+ android: 'mysigner sync android'
482
535
  },
483
- profiles: "mysigner profiles",
484
- certificates: "mysigner certificates",
485
- keystores: "mysigner keystores",
486
- doctor: "mysigner doctor",
487
- onboard: "mysigner onboard",
488
- login: "mysigner login"
536
+ profiles: 'mysigner profiles',
537
+ certificates: 'mysigner certificates',
538
+ keystores: 'mysigner keystore list',
539
+ doctor: 'mysigner doctor',
540
+ onboard: 'mysigner onboard',
541
+ login: 'mysigner login'
489
542
  }.freeze
490
543
 
491
544
  # Get quick reference command
492
545
  def quick_ref(operation, platform: nil)
493
546
  ref = QUICK_REFERENCE[operation]
494
547
  return ref unless ref.is_a?(Hash)
548
+
495
549
  platform ? ref[platform] : ref[:ios]
496
550
  end
497
551
  end