mysigner 0.1.0

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 +7 -0
  2. data/.DS_Store +0 -0
  3. data/.gitignore +12 -0
  4. data/.rspec +3 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +7 -0
  7. data/CODE_OF_CONDUCT.md +74 -0
  8. data/Gemfile +7 -0
  9. data/Gemfile.lock +137 -0
  10. data/LICENSE +201 -0
  11. data/MANUAL_TEST.md +341 -0
  12. data/README.md +493 -0
  13. data/Rakefile +6 -0
  14. data/bin/console +14 -0
  15. data/bin/setup +8 -0
  16. data/exe/mysigner +5 -0
  17. data/lib/mysigner/build/android_executor.rb +367 -0
  18. data/lib/mysigner/build/android_parser.rb +293 -0
  19. data/lib/mysigner/build/configurator.rb +126 -0
  20. data/lib/mysigner/build/detector.rb +388 -0
  21. data/lib/mysigner/build/error_analyzer.rb +193 -0
  22. data/lib/mysigner/build/executor.rb +176 -0
  23. data/lib/mysigner/build/parser.rb +206 -0
  24. data/lib/mysigner/cli/auth_commands.rb +1381 -0
  25. data/lib/mysigner/cli/build_commands.rb +2095 -0
  26. data/lib/mysigner/cli/concerns/actionable_suggestions.rb +500 -0
  27. data/lib/mysigner/cli/concerns/api_helpers.rb +131 -0
  28. data/lib/mysigner/cli/concerns/error_handlers.rb +446 -0
  29. data/lib/mysigner/cli/concerns/helpers.rb +63 -0
  30. data/lib/mysigner/cli/diagnostic_commands.rb +1034 -0
  31. data/lib/mysigner/cli/resource_commands.rb +2670 -0
  32. data/lib/mysigner/cli.rb +43 -0
  33. data/lib/mysigner/client.rb +189 -0
  34. data/lib/mysigner/config.rb +311 -0
  35. data/lib/mysigner/export/exporter.rb +150 -0
  36. data/lib/mysigner/signing/certificate_checker.rb +148 -0
  37. data/lib/mysigner/signing/keystore_manager.rb +239 -0
  38. data/lib/mysigner/signing/validator.rb +150 -0
  39. data/lib/mysigner/signing/wizard.rb +784 -0
  40. data/lib/mysigner/upload/app_store_automation.rb +402 -0
  41. data/lib/mysigner/upload/app_store_submission.rb +312 -0
  42. data/lib/mysigner/upload/play_store_uploader.rb +378 -0
  43. data/lib/mysigner/upload/uploader.rb +373 -0
  44. data/lib/mysigner/version.rb +3 -0
  45. data/lib/mysigner.rb +15 -0
  46. data/mysigner.gemspec +78 -0
  47. data/test_manual.rb +102 -0
  48. metadata +286 -0
@@ -0,0 +1,446 @@
1
+ module Mysigner
2
+ class CLI < Thor
3
+ module Concerns
4
+ module ErrorHandlers
5
+ # Show guidance for getting a token
6
+ def show_token_guidance(api_url)
7
+ say "Don't have a token yet?", :yellow
8
+ say " 1. Go to: #{api_url}", :cyan
9
+ say " 2. Navigate to: Your Organization → API Tokens", :cyan
10
+ say " 3. Click 'Create Token'", :cyan
11
+ say " 4. Copy the token (you'll only see it once!)", :cyan
12
+ say ""
13
+ say "💡 Or run 'mysigner onboard' for step-by-step guidance", :yellow
14
+ say ""
15
+ end
16
+
17
+ # Handle connection failure
18
+ def handle_connection_failure(api_url)
19
+ say ""
20
+ say "Possible issues:", :yellow
21
+ say " • API server is not running at #{api_url}"
22
+ say " • Network connectivity problems"
23
+ say " • Incorrect API URL"
24
+ say ""
25
+ say "💡 Try:", :cyan
26
+ say " • Check the API URL is correct"
27
+ say " • Verify the server is running"
28
+ say " • Run 'mysigner onboard' for guided setup"
29
+ end
30
+
31
+ # Show guidance for creating an organization
32
+ def show_create_org_guidance(api_url)
33
+ say "To create an organization:", :cyan
34
+ say " 1. Go to: #{api_url}"
35
+ say " 2. Sign in to your account"
36
+ say " 3. Click 'Create Organization'"
37
+ say " 4. Then generate a new API token for that organization"
38
+ say ""
39
+ say "💡 Run 'mysigner onboard' for step-by-step guidance", :yellow
40
+ end
41
+
42
+ # Handle unauthorized error
43
+ def handle_unauthorized_error(api_url)
44
+ say ""
45
+ say "=" * 80, :red
46
+ say "✗ Authentication Failed", :red
47
+ say "=" * 80, :red
48
+ say ""
49
+ say "Your API token is invalid or has been revoked.", :bold
50
+ say ""
51
+ say "Common reasons:", :yellow
52
+ say " • Token was copied incorrectly (missing characters)"
53
+ say " • Token was revoked in the web dashboard"
54
+ say " • Token has expired"
55
+ say " • You're using the wrong API URL"
56
+ say ""
57
+ say "To fix this:", :cyan
58
+ say " 1. Go to: #{api_url}/organizations/YOUR_ORG/api_tokens"
59
+ say " 2. Check if your token is still active"
60
+ say " 3. If revoked or expired, create a new token"
61
+ say " 4. Copy the NEW token carefully (entire string)"
62
+ say " 5. Run 'mysigner login' again"
63
+ say ""
64
+ say "💡 Or run 'mysigner onboard' for guided setup", :yellow
65
+ say ""
66
+ end
67
+
68
+ # Handle connection error
69
+ def handle_connection_error(error, api_url)
70
+ say ""
71
+ say "=" * 80, :red
72
+ say "✗ Connection Failed", :red
73
+ say "=" * 80, :red
74
+ say ""
75
+ say "Error: #{error.message}", :red
76
+ say ""
77
+ say "Possible causes:", :yellow
78
+ say " • My Signer API is not running at #{api_url}"
79
+ say " • Network connectivity issues"
80
+ say " • Firewall blocking the connection"
81
+ say " • Incorrect API URL"
82
+ say ""
83
+ say "To fix this:", :cyan
84
+ say ""
85
+ if api_url.include?('localhost')
86
+ say " For local development:", :bold
87
+ say " 1. Make sure Rails server is running:"
88
+ say " cd path/to/my-signer"
89
+ say " bin/rails server"
90
+ say ""
91
+ say " 2. Verify it's accessible:"
92
+ say " curl #{api_url}/up"
93
+ say ""
94
+ else
95
+ say " For production:", :bold
96
+ say " 1. Check the API URL is correct"
97
+ say " 2. Verify the service is running"
98
+ say " 3. Check your internet connection"
99
+ say ""
100
+ end
101
+ say " Or set a custom API URL:", :bold
102
+ say " export MYSIGNER_API_URL=http://your-server.com"
103
+ say ""
104
+ say "💡 Run 'mysigner onboard' to reconfigure", :yellow
105
+ say ""
106
+ end
107
+
108
+ # Handle unexpected error
109
+ def handle_unexpected_error(error, api_url)
110
+ say ""
111
+ say "=" * 80, :red
112
+ say "✗ Unexpected Error", :red
113
+ say "=" * 80, :red
114
+ say ""
115
+ say "Error: #{error.message}", :red
116
+ say "Type: #{error.class}", :red if ENV['DEBUG']
117
+ say ""
118
+ say "This is unexpected. Please try:", :yellow
119
+ say " 1. Run 'mysigner onboard' to reconfigure"
120
+ say " 2. Check #{api_url} is accessible"
121
+ say " 3. Run 'mysigner doctor' to check your environment"
122
+ say ""
123
+ if ENV['DEBUG']
124
+ say "Stack trace:", :red
125
+ say error.backtrace.first(5).join("\n"), :red
126
+ say ""
127
+ else
128
+ say "💡 For more details, run with DEBUG=1", :yellow
129
+ say ""
130
+ end
131
+ end
132
+
133
+ # Handle Apple API errors with actionable suggestions
134
+ def handle_apple_api_error(error, context: {})
135
+ error_message = error.message.to_s
136
+
137
+ say ""
138
+ say "=" * 80, :red
139
+ say "✗ #{context[:title] || 'Apple API Error'}", :red
140
+ say "=" * 80, :red
141
+ say ""
142
+ say "Error: #{error_message}", :red
143
+ say ""
144
+
145
+ # Check for specific Apple error patterns
146
+ if error_message =~ /no.*build.*found|build.*not.*found|no.*processed.*build/i
147
+ show_build_not_found_suggestions(context)
148
+ elsif error_message =~ /still.*processing|processing.*build/i
149
+ show_build_processing_suggestions
150
+ elsif error_message =~ /profile.*expired|provisioning.*expired|certificate.*expired/i
151
+ show_expired_credential_suggestions
152
+ elsif error_message =~ /profile.*not.*found|no.*provisioning.*profile|missing.*profile/i
153
+ show_profile_not_found_suggestions
154
+ elsif error_message =~ /certificate.*not.*found|no.*signing.*certificate/i
155
+ show_certificate_not_found_suggestions
156
+ elsif error_message =~ /app.*with.*bundle.*id.*not.*found|app.*not.*found/i
157
+ show_app_not_found_suggestions(context[:bundle_id])
158
+ elsif error_message =~ /missing.*required.*field|what's.*new.*required|cannot.*submit.*missing/i
159
+ show_missing_metadata_suggestions
160
+ elsif error_message =~ /archive.*not.*found|no.*xcarchive/i
161
+ show_archive_not_found_suggestions
162
+ elsif error_message =~ /ipa.*not.*found|no.*ipa.*file/i
163
+ show_ipa_not_found_suggestions
164
+ else
165
+ show_generic_apple_suggestions
166
+ end
167
+
168
+ show_saved_files(context)
169
+ show_debug_info(error)
170
+ end
171
+
172
+ # Handle Google Play API errors with actionable suggestions
173
+ def handle_android_api_error(error, context: {})
174
+ error_message = error.message.to_s
175
+
176
+ say ""
177
+ say "=" * 80, :red
178
+ say "✗ #{context[:title] || 'Google Play API Error'}", :red
179
+ say "=" * 80, :red
180
+ say ""
181
+ say "Error: #{error_message}", :red
182
+ say ""
183
+
184
+ # Check for specific Google Play error patterns
185
+ if error_message =~ /keystore.*not.*found|no.*keystore|missing.*keystore/i
186
+ show_keystore_not_found_suggestions
187
+ elsif error_message =~ /keystore.*password|wrong.*password/i
188
+ show_keystore_password_suggestions
189
+ elsif error_message =~ /package.*not.*found|first.*build.*uploaded.*manually/i
190
+ show_first_upload_suggestions(context[:package_name])
191
+ elsif error_message =~ /version.*code.*already|already.*used/i
192
+ show_version_code_conflict_suggestions
193
+ elsif error_message =~ /service.*account.*not.*found|no.*credentials/i
194
+ show_service_account_missing_suggestions
195
+ elsif error_message =~ /not.*authorized|permission.*denied|forbidden/i
196
+ show_permission_denied_suggestions
197
+ elsif error_message =~ /precondition.*failed|track.*not.*ready/i
198
+ show_track_not_setup_suggestions(context[:track])
199
+ elsif error_message =~ /aab.*not.*found|no.*aab.*file/i
200
+ show_aab_not_found_suggestions
201
+ else
202
+ show_generic_android_suggestions
203
+ end
204
+
205
+ show_saved_files(context)
206
+ show_debug_info(error)
207
+ end
208
+
209
+ private
210
+
211
+ # iOS-specific suggestion helpers
212
+ def show_build_not_found_suggestions(context)
213
+ say "💡 Build Not Found: How to fix", :cyan
214
+ say ""
215
+ say " → Upload a build first: mysigner ship testflight", :yellow
216
+ say " → If already uploaded, wait 5-15 minutes for Apple to process", :yellow
217
+ say " → Use --wait flag to poll: mysigner ship appstore --wait", :yellow
218
+ say " → Sync your builds: mysigner sync ios", :yellow
219
+ say ""
220
+ end
221
+
222
+ def show_build_processing_suggestions
223
+ say "💡 Build Still Processing: How to fix", :cyan
224
+ say ""
225
+ say " → Apple typically takes 5-15 minutes to process builds", :yellow
226
+ say " → Use --wait flag: mysigner ship appstore --wait", :yellow
227
+ say " → Increase timeout: --asc-timeout-seconds 1800", :yellow
228
+ say " → Check App Store Connect for processing status", :yellow
229
+ say ""
230
+ end
231
+
232
+ def show_expired_credential_suggestions
233
+ say "💡 Expired Profile or Certificate: How to fix", :cyan
234
+ say ""
235
+ say " → List profiles with expiration dates: mysigner profiles", :yellow
236
+ say " → Check status in My Signer dashboard", :yellow
237
+ say " → Regenerate in Apple Developer Portal", :yellow
238
+ say " → Download fresh profile: mysigner profile download <ID>", :yellow
239
+ say ""
240
+ end
241
+
242
+ def show_profile_not_found_suggestions
243
+ say "💡 Provisioning Profile Not Found: How to fix", :cyan
244
+ say ""
245
+ say " → List available profiles: mysigner profiles", :yellow
246
+ say " → Sync from Apple: mysigner sync ios", :yellow
247
+ say " → Create profile in Apple Developer Portal", :yellow
248
+ say " → Check if profile matches your Bundle ID", :yellow
249
+ say ""
250
+ end
251
+
252
+ def show_certificate_not_found_suggestions
253
+ say "💡 Signing Certificate Not Found: How to fix", :cyan
254
+ say ""
255
+ say " → List certificates: mysigner certificates", :yellow
256
+ say " → Download and install: mysigner certificate download <ID>", :yellow
257
+ say " → Check Keychain Access for installed certificates", :yellow
258
+ say " → Run: mysigner doctor (diagnose signing issues)", :yellow
259
+ say ""
260
+ end
261
+
262
+ def show_app_not_found_suggestions(bundle_id = nil)
263
+ say "💡 App Not Found: How to fix", :cyan
264
+ say ""
265
+ say " → Ensure app exists in App Store Connect", :yellow
266
+ say " → Create app in App Store Connect first", :yellow
267
+ say " → Verify Bundle ID matches your Xcode project", :yellow if bundle_id
268
+ say " → Sync from App Store Connect: mysigner sync ios", :yellow
269
+ say ""
270
+ end
271
+
272
+ def show_missing_metadata_suggestions
273
+ say "💡 Missing App Store Metadata: How to fix", :cyan
274
+ say ""
275
+ say " → Configure release in My Signer dashboard", :yellow
276
+ say " → Provide What's New via CLI: --whats-new \"Your text\"", :yellow
277
+ say " → Ensure support URL is set in App Store Connect", :yellow
278
+ say " → Complete app information in App Store Connect", :yellow
279
+ say ""
280
+ end
281
+
282
+ def show_archive_not_found_suggestions
283
+ say "💡 Archive Not Found: How to fix", :cyan
284
+ say ""
285
+ say " → Build first: mysigner build", :yellow
286
+ say " → Or use: mysigner ship testflight (handles build)", :yellow
287
+ say " → Check if Xcode build succeeded", :yellow
288
+ say ""
289
+ end
290
+
291
+ def show_ipa_not_found_suggestions
292
+ say "💡 IPA File Not Found: How to fix", :cyan
293
+ say ""
294
+ say " → Export IPA: mysigner export <archive_path>", :yellow
295
+ say " → Or use: mysigner ship testflight (handles export)", :yellow
296
+ say " → Check export method matches profile type", :yellow
297
+ say ""
298
+ end
299
+
300
+ def show_generic_apple_suggestions
301
+ say "💡 General troubleshooting:", :cyan
302
+ say ""
303
+ say " → Run 'mysigner doctor' to check your setup", :yellow
304
+ say " → Sync from Apple: mysigner sync ios", :yellow
305
+ say " → Check App Store Connect: https://appstoreconnect.apple.com", :yellow
306
+ say ""
307
+ end
308
+
309
+ # Android-specific suggestion helpers
310
+ def show_keystore_not_found_suggestions
311
+ say "💡 Keystore Not Found: How to fix", :cyan
312
+ say ""
313
+ say " → Upload keystore: mysigner keystore upload <path>", :yellow
314
+ say " → List keystores: mysigner keystores", :yellow
315
+ say " → Download keystore: mysigner keystore download <ID>", :yellow
316
+ say " → Check keystore path in build.gradle", :yellow
317
+ say ""
318
+ end
319
+
320
+ def show_keystore_password_suggestions
321
+ say "💡 Keystore Password Issue: How to fix", :cyan
322
+ say ""
323
+ say " → Verify keystore password is correct", :yellow
324
+ say " → Check password in My Signer dashboard", :yellow
325
+ say " → Update password: mysigner keystore update <ID>", :yellow
326
+ say ""
327
+ end
328
+
329
+ def show_first_upload_suggestions(package_name = nil)
330
+ say "💡 First Upload Required: How to fix", :cyan
331
+ say ""
332
+ say " Google Play requires the FIRST build to be uploaded manually:", :yellow
333
+ say ""
334
+ say " 1. Build AAB: mysigner android build", :yellow
335
+ say " 2. Go to Play Console → Your App → Internal testing", :yellow
336
+ say " 3. Click 'Create release' and upload the AAB", :yellow
337
+ say " 4. Save the release (no need to roll out)", :yellow
338
+ say ""
339
+ say " After that, 'mysigner ship' will work for future uploads.", :green
340
+ say ""
341
+ end
342
+
343
+ def show_version_code_conflict_suggestions
344
+ say "💡 Version Code Conflict: How to fix", :cyan
345
+ say ""
346
+ say " → Version code already exists on Google Play", :yellow
347
+ say " → Run the command again - mysigner auto-increments", :yellow
348
+ say " → Or manually increment versionCode in build.gradle", :yellow
349
+ say ""
350
+ end
351
+
352
+ def show_service_account_missing_suggestions
353
+ say "💡 Service Account Not Found: How to fix", :cyan
354
+ say ""
355
+ say " Set up Google Play credentials in My Signer dashboard:", :yellow
356
+ say ""
357
+ say " 1. Go to Play Console → API access → Service accounts", :yellow
358
+ say " 2. Create a service account with Editor access", :yellow
359
+ say " 3. Download the JSON key", :yellow
360
+ say " 4. Upload to My Signer dashboard → Google Play Settings", :yellow
361
+ say ""
362
+ end
363
+
364
+ def show_permission_denied_suggestions
365
+ say "💡 Service Account Permission Denied: How to fix", :cyan
366
+ say ""
367
+ say " In Play Console → API access:", :yellow
368
+ say ""
369
+ say " 1. Find your service account", :yellow
370
+ say " 2. Click 'Manage Play Console permissions'", :yellow
371
+ say " 3. Grant 'Admin' or 'Release manager' access", :yellow
372
+ say ""
373
+ say " Note: Permission changes take ~15 minutes to propagate", :green
374
+ say ""
375
+ end
376
+
377
+ def show_track_not_setup_suggestions(track = nil)
378
+ track_name = track || "this track"
379
+ say "💡 Track Not Set Up in Play Console: How to fix", :cyan
380
+ say ""
381
+ say " Complete track setup in Google Play Console:", :yellow
382
+ say ""
383
+ say " For PRODUCTION:", :yellow
384
+ say " • Complete store listing, content rating, pricing", :yellow
385
+ say ""
386
+ say " For BETA/ALPHA:", :yellow
387
+ say " • Create testing track and add testers", :yellow
388
+ say ""
389
+ say " For INTERNAL:", :yellow
390
+ say " • Add internal testers", :yellow
391
+ say ""
392
+ say " ✓ Your AAB was uploaded successfully!", :green
393
+ say " → Go to Play Console to finish track setup", :green
394
+ say ""
395
+ end
396
+
397
+ def show_aab_not_found_suggestions
398
+ say "💡 AAB File Not Found: How to fix", :cyan
399
+ say ""
400
+ say " → Build your app first: mysigner android build", :yellow
401
+ say " → Or use: mysigner ship internal (handles build)", :yellow
402
+ say " → Check if Gradle build succeeded", :yellow
403
+ say ""
404
+ end
405
+
406
+ def show_generic_android_suggestions
407
+ say "💡 General troubleshooting:", :cyan
408
+ say ""
409
+ say " → Run 'mysigner doctor' to check your setup", :yellow
410
+ say " → List keystores: mysigner keystores", :yellow
411
+ say " → Check Play Console: https://play.google.com/console", :yellow
412
+ say ""
413
+ end
414
+
415
+ # Helper to show saved file paths
416
+ def show_saved_files(context)
417
+ if context[:archive_path] && File.exist?(context[:archive_path])
418
+ say "📦 Archive saved at: #{context[:archive_path]}", :yellow
419
+ end
420
+ if context[:ipa_path] && File.exist?(context[:ipa_path])
421
+ say "📦 IPA saved at: #{context[:ipa_path]}", :yellow
422
+ end
423
+ if context[:aab_path] && File.exist?(context[:aab_path])
424
+ say "📦 AAB saved at: #{context[:aab_path]}", :yellow
425
+ end
426
+ end
427
+
428
+ # Helper to show debug info
429
+ def show_debug_info(error)
430
+ say ""
431
+ if ENV['DEBUG']
432
+ say "Debug info:", :yellow
433
+ say " Error class: #{error.class}", :yellow
434
+ say " Backtrace:", :yellow
435
+ error.backtrace&.first(5)&.each do |line|
436
+ say " #{line}", :yellow
437
+ end
438
+ else
439
+ say "💡 For more details, run with DEBUG=1", :yellow
440
+ end
441
+ say ""
442
+ end
443
+ end
444
+ end
445
+ end
446
+ end
@@ -0,0 +1,63 @@
1
+ module Mysigner
2
+ class CLI < Thor
3
+ module Concerns
4
+ module Helpers
5
+ # Helper for timing operations
6
+ def with_timing(label)
7
+ start = Time.now
8
+ result = yield
9
+ duration = Time.now - start
10
+ [result, duration]
11
+ end
12
+
13
+ def format_duration(seconds)
14
+ if seconds < 60
15
+ "#{seconds.round}s"
16
+ elsif seconds < 3600
17
+ minutes = (seconds / 60).floor
18
+ secs = (seconds % 60).round
19
+ "#{minutes}m #{secs}s"
20
+ else
21
+ hours = (seconds / 3600).floor
22
+ minutes = ((seconds % 3600) / 60).floor
23
+ "#{hours}h #{minutes}m"
24
+ end
25
+ end
26
+
27
+ def format_bytes(bytes)
28
+ if bytes < 1024
29
+ "#{bytes} B"
30
+ elsif bytes < 1024 * 1024
31
+ "#{(bytes / 1024.0).round(1)} KB"
32
+ else
33
+ "#{(bytes / (1024.0 * 1024)).round(1)} MB"
34
+ end
35
+ end
36
+
37
+ def load_config
38
+ config = Config.new
39
+
40
+ unless config.exists?
41
+ error "Not logged in. Run 'mysigner login' first."
42
+ exit 1
43
+ end
44
+
45
+ config.load
46
+ config
47
+ end
48
+
49
+ def create_client(config)
50
+ Client.new(
51
+ api_url: config.api_url,
52
+ api_token: config.api_token,
53
+ user_email: config.user_email
54
+ )
55
+ end
56
+
57
+ def error(message)
58
+ say "✗ Error: #{message}", :red
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end