mysigner 0.1.0 → 0.1.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 95c182f73aa3a1ae392f084caa9f18d55e82a94939facf19bdab2c3a7f431f1b
4
- data.tar.gz: 02e6462deb9b7da34eb7a95e14065d931b73891f5e2eb5f88449188ced09c346
3
+ metadata.gz: f8cd24e9a29c341d204f6cc2d9c5cde99c6248b655bf004fa370daa78abc9cd1
4
+ data.tar.gz: 92e37eff120a9a3ca8325d40b720c2878339735a368fae5cbe917017682825d7
5
5
  SHA512:
6
- metadata.gz: 6246b6206c94a912552401259184267214d28ed1f65050e7f4a6c1ce9ba1190d8312b85e5d7055a5b657d2a4001d1810b62b2414d56929895925a5cfb00510cd
7
- data.tar.gz: 2cccea5a3fe1d78071844d2237d54ada81284eb4b3494f0ccca9f9439e74d87f97d76d9a8f3c2880fd05d99c921b0aa6c72eca0d86b11f14fdf5601f00ef230f
6
+ metadata.gz: b81c29b7b84b9357d0419c9333d0f8b769e475366fb5d73d5baa278c7d37e0e5f2a788b2e742f9dff731a5df821de09f1b651498ec496520b7600988d60c22de
7
+ data.tar.gz: f035dbf8191e61be0f411a3ed8f8894f1cb725ed3b804f3dc6a33e9be466870af2a74cb150e1551b42a4e3862b6cd4feb9858c27d0a3e36fcea72a55a372df74
data/.DS_Store CHANGED
Binary file
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- mysigner (0.1.0)
4
+ mysigner (0.1.1)
5
5
  base64 (~> 0.2)
6
6
  faraday (~> 2.14)
7
7
  faraday-retry (~> 2.2)
@@ -105,7 +105,7 @@ module Mysigner
105
105
  params: {
106
106
  bundle_id: bundle_id,
107
107
  type: profile_type.upcase,
108
- status: 'ACTIVE'
108
+ state: 'ACTIVE'
109
109
  }
110
110
  )
111
111
 
@@ -11,8 +11,8 @@ module Mysigner
11
11
  say "Install: #{File.expand_path('../../../..', __FILE__)}", :white
12
12
  say "Config: #{Config::CONFIG_FILE}", :white
13
13
  say ""
14
- say "Repository: https://github.com/yourusername/my-signer-cli", :white
15
- say "Issues: https://github.com/yourusername/my-signer-cli/issues", :white
14
+ say "Docs: https://mysigner.dev/docs/commands", :white
15
+ say "Support: https://mysigner.dev/landing#contact", :white
16
16
  end
17
17
 
18
18
  desc "login", "Log in with existing API token (⭐ first-timers: use 'onboard' instead)"
@@ -225,7 +225,7 @@ module Mysigner
225
225
 
226
226
  # Check App Store Connect status
227
227
  begin
228
- client = Client.new(api_url: config.api_url, api_token: config.api_token)
228
+ client = Client.new(api_url: config.api_url, api_token: config.api_token, user_email: config.user_email)
229
229
  org_response = client.get("/api/v1/organizations/#{config.current_organization_id}")
230
230
  org_data = org_response[:data]
231
231
 
@@ -268,11 +268,11 @@ module Mysigner
268
268
 
269
269
  begin
270
270
  app_response = client.get("/api/v1/organizations/#{config.current_organization_id}/apple_apps", params: { bundle_id: bundle_id })
271
- app = Array(app_response[:data]['data']['apps']).first
272
-
271
+ app = Array(app_response.dig(:data, 'data', 'apps')).first
272
+
273
273
  if app
274
274
  builds_response = client.get("/api/v1/organizations/#{config.current_organization_id}/builds", params: { app_id: app['id'] })
275
- latest = Array(builds_response[:data]['data']['builds']).first
275
+ latest = Array(builds_response.dig(:data, 'data', 'builds')).first
276
276
  if latest
277
277
  latest_build_before_upload = latest['build_number'].to_i
278
278
  say "✓ Current latest build: ##{latest_build_before_upload}", :green
@@ -375,11 +375,11 @@ module Mysigner
375
375
  # Check for new build
376
376
  begin
377
377
  app_response = client.get("/api/v1/organizations/#{config.current_organization_id}/apple_apps", params: { bundle_id: bundle_id })
378
- app = Array(app_response[:data]['data']['apps']).first
379
-
378
+ app = Array(app_response.dig(:data, 'data', 'apps')).first
379
+
380
380
  if app
381
381
  builds_response = client.get("/api/v1/organizations/#{config.current_organization_id}/builds", params: { app_id: app['id'] })
382
- latest = Array(builds_response[:data]['data']['builds']).first
382
+ latest = Array(builds_response.dig(:data, 'data', 'builds')).first
383
383
 
384
384
  current_build_num = latest ? latest['build_number'].to_i : 0
385
385
 
@@ -864,7 +864,7 @@ module Mysigner
864
864
  begin
865
865
  client.post(
866
866
  "/api/v1/organizations/#{config.current_organization_id}/android_keystores/#{active_keystore['id']}/link_to_app",
867
- { package_name: package_name }
867
+ body: { package_name: package_name }
868
868
  )
869
869
  rescue => e
870
870
  # Non-fatal, continue
@@ -1024,15 +1024,15 @@ module Mysigner
1024
1024
  friendly_name = generate_app_name_from_package(package_name)
1025
1025
  create_response = client.post(
1026
1026
  "/api/v1/organizations/#{config.current_organization_id}/android_apps",
1027
- { android_app: { package_name: package_name, name: friendly_name } }
1027
+ body: { android_app: { package_name: package_name, name: friendly_name } }
1028
1028
  )
1029
- app = create_response[:data]
1029
+ app = create_response[:data]['android_app']
1030
1030
  end
1031
1031
 
1032
1032
  # Now save the build record
1033
1033
  client.post(
1034
1034
  "/api/v1/organizations/#{config.current_organization_id}/android_apps/#{app['id']}/android_builds",
1035
- { android_build: { version_code: version_code, version_name: version_name, status: 'completed' } }
1035
+ body: { android_build: { version_code: version_code, version_name: version_name, status: 'completed' } }
1036
1036
  )
1037
1037
  rescue => e
1038
1038
  # Non-fatal - just log for debugging
@@ -80,7 +80,7 @@ module Mysigner
80
80
  say " ✓ Logged in", :green
81
81
 
82
82
  begin
83
- client = Client.new(api_url: config.api_url, api_token: config.api_token)
83
+ client = Client.new(api_url: config.api_url, api_token: config.api_token, user_email: config.user_email)
84
84
  client.test_connection
85
85
  say " ✓ API connection working", :green
86
86
 
@@ -891,14 +891,14 @@ module Mysigner
891
891
  )
892
892
 
893
893
  if response[:success]
894
- data = response[:data]
894
+ data = response[:data]['data'] || response[:data]
895
895
  say "✓ iOS sync completed!", :green
896
896
  say ""
897
-
897
+
898
898
  if data['synced_at']
899
899
  say "Last synced: #{data['synced_at']}", :cyan
900
900
  end
901
-
901
+
902
902
  if data['summary']
903
903
  say ""
904
904
  say "📊 Summary:", :cyan
@@ -50,10 +50,12 @@ module Mysigner
50
50
  end
51
51
 
52
52
  # Show pagination
53
- say "Page #{pagination['page']} of #{pagination['total_pages']} (#{pagination['total']} total)", :yellow
54
-
55
- if pagination['page'] < pagination['total_pages']
56
- say "Run with --page #{pagination['page'] + 1} to see more", :yellow
53
+ if pagination
54
+ say "Page #{pagination['page']} of #{pagination['total_pages']} (#{pagination['total']} total)", :yellow
55
+
56
+ if pagination['page'] < pagination['total_pages']
57
+ say "Run with --page #{pagination['page'] + 1} to see more", :yellow
58
+ end
57
59
  end
58
60
  rescue Mysigner::ClientError => e
59
61
  error "Failed to fetch devices: #{e.message}"
@@ -172,6 +174,7 @@ module Mysigner
172
174
  else
173
175
  say " #{e.message}", :red
174
176
  end
177
+ say " Suggestion: #{e.suggestion}", :yellow if e.suggestion
175
178
  exit 1
176
179
  rescue Mysigner::ClientError => e
177
180
  if e.message.include?("already exists")
@@ -465,13 +468,13 @@ module Mysigner
465
468
 
466
469
  # Display profiles
467
470
  profiles.each do |profile|
468
- status_icon = profile['status'] == 'ACTIVE' ? '✓' : '✗'
469
- status_color = profile['status'] == 'ACTIVE' ? :green : :red
470
-
471
+ status_icon = profile['state'] == 'ACTIVE' ? '✓' : '✗'
472
+ status_color = profile['state'] == 'ACTIVE' ? :green : :red
473
+
471
474
  say " #{status_icon} #{profile['name']}", status_color
472
475
  say " ID: #{profile['id']} | Type: #{profile['profile_type'] || 'N/A'}"
473
- say " Bundle ID: #{profile['bundle_id'] || 'N/A'}"
474
- say " Status: #{profile['status'] || 'UNKNOWN'}"
476
+ say " Bundle ID: #{profile['bundle_id_identifier'] || 'N/A'}"
477
+ say " Status: #{profile['state'] || 'UNKNOWN'}"
475
478
 
476
479
  if profile['expires_at']
477
480
  expires = Time.parse(profile['expires_at']).strftime('%Y-%m-%d')
@@ -482,10 +485,12 @@ module Mysigner
482
485
  end
483
486
 
484
487
  # Show pagination
485
- say "Page #{pagination['page']} of #{pagination['total_pages']} (#{pagination['total']} total)", :yellow
486
-
487
- if pagination['page'] < pagination['total_pages']
488
- say "Run with --page #{pagination['page'] + 1} to see more", :yellow
488
+ if pagination
489
+ say "Page #{pagination['page']} of #{pagination['total_pages']} (#{pagination['total']} total)", :yellow
490
+
491
+ if pagination['page'] < pagination['total_pages']
492
+ say "Run with --page #{pagination['page'] + 1} to see more", :yellow
493
+ end
489
494
  end
490
495
  rescue Mysigner::ClientError => e
491
496
  error "Failed to fetch profiles: #{e.message}"
@@ -609,6 +614,7 @@ module Mysigner
609
614
  # Use Faraday directly with proper auth for binary download
610
615
  conn = Faraday.new(url: config.api_url) do |f|
611
616
  f.request :authorization, 'Bearer', config.api_token
617
+ f.headers['X-User-Email'] = config.user_email if config.user_email
612
618
  f.adapter Faraday.default_adapter
613
619
  end
614
620
 
@@ -780,10 +786,12 @@ module Mysigner
780
786
  end
781
787
 
782
788
  # Show pagination
783
- say "Page #{pagination['page']} of #{pagination['total_pages']} (#{pagination['total']} total)", :yellow
784
-
785
- if pagination['page'] < pagination['total_pages']
786
- say "Run with --page #{pagination['page'] + 1} to see more", :yellow
789
+ if pagination
790
+ say "Page #{pagination['page']} of #{pagination['total_pages']} (#{pagination['total']} total)", :yellow
791
+
792
+ if pagination['page'] < pagination['total_pages']
793
+ say "Run with --page #{pagination['page'] + 1} to see more", :yellow
794
+ end
787
795
  end
788
796
  rescue Mysigner::ClientError => e
789
797
  error "Failed to fetch certificates: #{e.message}"
@@ -937,6 +945,7 @@ module Mysigner
937
945
  # Use Faraday directly with proper auth for binary download
938
946
  conn = Faraday.new(url: config.api_url) do |f|
939
947
  f.request :authorization, 'Bearer', config.api_token
948
+ f.headers['X-User-Email'] = config.user_email if config.user_email
940
949
  f.adapter Faraday.default_adapter
941
950
  end
942
951
 
@@ -1472,10 +1481,10 @@ module Mysigner
1472
1481
  begin
1473
1482
  response = client.post(
1474
1483
  "/api/v1/organizations/#{config.current_organization_id}/android_apps",
1475
- body: {
1484
+ body: { android_app: {
1476
1485
  package_name: package_name,
1477
1486
  name: app_name
1478
- }
1487
+ } }
1479
1488
  )
1480
1489
 
1481
1490
  app = response[:data]['android_app'] || response[:data]
@@ -1501,6 +1510,7 @@ module Mysigner
1501
1510
  else
1502
1511
  say " #{e.message}", :red
1503
1512
  end
1513
+ say " Suggestion: #{e.suggestion}", :yellow if e.suggestion
1504
1514
  exit 1
1505
1515
  rescue Mysigner::ClientError => e
1506
1516
  error "Failed to register app: #{e.message}"
@@ -1518,10 +1528,10 @@ module Mysigner
1518
1528
  begin
1519
1529
  response = client.post(
1520
1530
  "/api/v1/organizations/#{config.current_organization_id}/android_apps",
1521
- body: {
1531
+ body: { android_app: {
1522
1532
  package_name: package_name,
1523
1533
  name: name
1524
- }.compact
1534
+ }.compact }
1525
1535
  )
1526
1536
 
1527
1537
  app = response[:data]['android_app'] || response[:data]
@@ -1551,6 +1561,7 @@ module Mysigner
1551
1561
  else
1552
1562
  say " #{e.message}", :red
1553
1563
  end
1564
+ say " Suggestion: #{e.suggestion}", :yellow if e.suggestion
1554
1565
  exit 1
1555
1566
  rescue Mysigner::ClientError => e
1556
1567
  if e.message.include?("already exists") || e.message.include?("taken")
@@ -1840,8 +1851,8 @@ module Mysigner
1840
1851
  config.load
1841
1852
  return nil unless config.api_token && config.organization_id
1842
1853
 
1843
- client = Mysigner::Client.new(api_url: config.api_url, api_token: config.api_token)
1844
-
1854
+ client = Mysigner::Client.new(api_url: config.api_url, api_token: config.api_token, user_email: config.user_email)
1855
+
1845
1856
  # Find app by package name
1846
1857
  response = client.get("/api/v1/organizations/#{config.organization_id}/android_apps")
1847
1858
  apps = response[:data]['android_apps'] || []
@@ -1860,7 +1871,7 @@ module Mysigner
1860
1871
  config.load
1861
1872
  return nil unless config.api_token && config.organization_id
1862
1873
 
1863
- client = Mysigner::Client.new(api_url: config.api_url, api_token: config.api_token)
1874
+ client = Mysigner::Client.new(api_url: config.api_url, api_token: config.api_token, user_email: config.user_email)
1864
1875
  keystore_manager = Signing::KeystoreManager.new(client, config.organization_id)
1865
1876
 
1866
1877
  # Find app by package name to get its keystore
@@ -2163,6 +2174,7 @@ module Mysigner
2163
2174
  else
2164
2175
  say " #{e.message}", :red
2165
2176
  end
2177
+ say " Suggestion: #{e.suggestion}", :yellow if e.suggestion
2166
2178
  exit 1
2167
2179
  rescue Mysigner::ClientError => e
2168
2180
  if e.message.include?("already exists") || e.message.include?("ENTITY_ERROR.ATTRIBUTE.INVALID.DUPLICATE")
@@ -2493,6 +2505,178 @@ module Mysigner
2493
2505
  end
2494
2506
  end
2495
2507
 
2508
+ # ==================== ANDROID TRACKS ====================
2509
+
2510
+ desc "tracks PACKAGE_NAME", "List Google Play tracks for an Android app"
2511
+ method_option :sort, type: :boolean, desc: 'Sort by track name'
2512
+ def tracks(package_name = nil)
2513
+ config = load_config
2514
+ client = create_client(config)
2515
+
2516
+ 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
2523
+ exit 1
2524
+ end
2525
+
2526
+ say "🎯 Google Play Tracks for #{package_name}", :cyan
2527
+ say ""
2528
+
2529
+ begin
2530
+ response = client.get("/api/v1/organizations/#{config.current_organization_id}/android_apps/package/#{package_name}/tracks")
2531
+ tracks = response[:data]['tracks'] || []
2532
+
2533
+ 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
2537
+ say "and sync with: mysigner sync android --package #{package_name}", :cyan
2538
+ return
2539
+ end
2540
+
2541
+ # Sort by track name if requested
2542
+ tracks = tracks.sort_by { |t| t['track_name'] } if options[:sort]
2543
+
2544
+ tracks.each do |track|
2545
+ status_color = track['status'] == 'completed' ? :green : :yellow
2546
+ say " 📍 #{track['track_name']}", :white
2547
+ say " Status: #{track['status'] || 'unknown'}", status_color
2548
+
2549
+ # Show releases info if available
2550
+ if track['releases'].is_a?(Array) && track['releases'].any?
2551
+ releases = track['releases']
2552
+ latest = releases.first
2553
+ version_codes = latest['versionCodes'] || latest['version_codes'] || []
2554
+ say " Version codes: #{version_codes.join(', ')}" if version_codes.any?
2555
+ say " Release status: #{latest['status']}" if latest['status']
2556
+ end
2557
+
2558
+ if track['updated_at']
2559
+ updated = Time.parse(track['updated_at']).strftime('%Y-%m-%d %H:%M')
2560
+ say " Updated: #{updated}"
2561
+ end
2562
+ say ""
2563
+ end
2564
+
2565
+ say "Total: #{tracks.count} track(s)", :yellow
2566
+ rescue Mysigner::NotFoundError => e
2567
+ if e.message.include?("Android app")
2568
+ 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
2573
+ say " → Register the app: mysigner android add #{package_name}", :yellow
2574
+ else
2575
+ error "Not found: #{e.message}"
2576
+ end
2577
+ exit 1
2578
+ rescue Mysigner::ClientError => e
2579
+ error "Failed to fetch tracks: #{e.message}"
2580
+ exit 1
2581
+ end
2582
+ end
2583
+
2584
+ desc "track PACKAGE_NAME TRACK_NAME", "Show details for a specific Google Play track"
2585
+ def track(package_name = nil, track_name = nil)
2586
+ config = load_config
2587
+ client = create_client(config)
2588
+
2589
+ 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
2599
+ exit 1
2600
+ end
2601
+
2602
+ say "🎯 Track: #{track_name}", :cyan
2603
+ say " Package: #{package_name}", :white
2604
+ say ""
2605
+
2606
+ begin
2607
+ response = client.get("/api/v1/organizations/#{config.current_organization_id}/android_apps/package/#{package_name}/tracks/#{track_name}")
2608
+ track = response[:data]
2609
+
2610
+ say "Details:", :bold
2611
+ say " Track Name: #{track['track_name']}"
2612
+ say " Status: #{track['status'] || 'unknown'}"
2613
+
2614
+ if track['updated_at']
2615
+ updated = Time.parse(track['updated_at']).strftime('%Y-%m-%d %H:%M')
2616
+ say " Last Updated: #{updated}"
2617
+ end
2618
+
2619
+ # Show releases info
2620
+ if track['releases'].is_a?(Array) && track['releases'].any?
2621
+ say ""
2622
+ say "Releases:", :bold
2623
+ track['releases'].each_with_index do |release, idx|
2624
+ say " Release #{idx + 1}:", :white
2625
+ say " Status: #{release['status']}" if release['status']
2626
+
2627
+ version_codes = release['versionCodes'] || release['version_codes'] || []
2628
+ say " Version Codes: #{version_codes.join(', ')}" if version_codes.any?
2629
+
2630
+ if release['name']
2631
+ say " Name: #{release['name']}"
2632
+ end
2633
+
2634
+ if release['releaseNotes'] || release['release_notes']
2635
+ notes = release['releaseNotes'] || release['release_notes']
2636
+ if notes.is_a?(Array) && notes.any?
2637
+ say " Release Notes:"
2638
+ notes.each do |note|
2639
+ lang = note['language'] || 'en-US'
2640
+ text = note['text'] || ''
2641
+ say " [#{lang}] #{text.slice(0, 80)}#{'...' if text.length > 80}"
2642
+ end
2643
+ end
2644
+ end
2645
+
2646
+ if release['userFraction'] || release['user_fraction']
2647
+ fraction = release['userFraction'] || release['user_fraction']
2648
+ say " Rollout: #{(fraction * 100).round(1)}%"
2649
+ end
2650
+ end
2651
+ else
2652
+ say ""
2653
+ say "No releases found in this track", :yellow
2654
+ end
2655
+
2656
+ rescue Mysigner::NotFoundError => e
2657
+ if e.message.include?("Android app")
2658
+ 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")
2664
+ error "Track not found: #{track_name}"
2665
+ say ""
2666
+ say "💡 Track not found:", :cyan
2667
+ say " → Check the track name is correct", :yellow
2668
+ say " → List available tracks: mysigner tracks #{package_name}", :yellow
2669
+ say " → Common tracks: production, beta, alpha, internal", :yellow
2670
+ else
2671
+ error "Not found: #{e.message}"
2672
+ end
2673
+ exit 1
2674
+ rescue Mysigner::ClientError => e
2675
+ error "Failed to fetch track: #{e.message}"
2676
+ exit 1
2677
+ end
2678
+ end
2679
+
2496
2680
  # ==================== APP GROUPS ====================
2497
2681
 
2498
2682
  desc "app-groups", "List App Groups"
@@ -79,7 +79,7 @@ module Mysigner
79
79
  interval: 0.5,
80
80
  interval_randomness: 0.5,
81
81
  backoff_factor: 2,
82
- retry_statuses: [429, 500, 502, 503, 504],
82
+ retry_statuses: [429, 502, 503, 504],
83
83
  methods: [:get, :post, :patch, :delete]
84
84
  }
85
85
 
@@ -113,22 +113,28 @@ module Mysigner
113
113
  def handle_error_response(response)
114
114
  error_data = response.body.is_a?(Hash) ? response.body : {}
115
115
  error_message = error_data['message'] || error_data['error'] || 'Unknown error'
116
+ suggestion = error_data['suggestion']
117
+
118
+ error_code = error_data['error']
119
+ timestamp = error_data['timestamp']
116
120
 
117
121
  case response.status
118
122
  when 401
119
- raise UnauthorizedError, "Unauthorized: #{error_message}"
123
+ raise UnauthorizedError.new("Unauthorized: #{error_message}", error_code: error_code, suggestion: suggestion, details: error_data['details'], timestamp: timestamp)
120
124
  when 403
121
- raise ForbiddenError, "Forbidden: #{error_message}"
125
+ raise ForbiddenError.new("Forbidden: #{error_message}", error_code: error_code, suggestion: suggestion, details: error_data['details'], timestamp: timestamp)
122
126
  when 404
123
- raise NotFoundError, "Not found: #{error_message}"
127
+ raise NotFoundError.new("Not found: #{error_message}", error_code: error_code, suggestion: suggestion, details: error_data['details'], timestamp: timestamp)
128
+ when 409
129
+ raise ValidationError.new(error_message, error_data['details'], suggestion: suggestion, error_code: error_code, timestamp: timestamp)
124
130
  when 422
125
- raise ValidationError.new(error_message, error_data['details'])
131
+ raise ValidationError.new(error_message, error_data['details'], suggestion: suggestion, error_code: error_code, timestamp: timestamp)
126
132
  when 429
127
133
  raise RateLimitError.new(error_message, error_data['retry_after'])
128
134
  when 500..599
129
- raise ServerError, "Server error (#{response.status}): #{error_message}"
135
+ raise ServerError.new("Server error (#{response.status}): #{error_message}", error_code: error_code, timestamp: timestamp)
130
136
  else
131
- raise ClientError, "Request failed (#{response.status}): #{error_message}"
137
+ raise ClientError.new("Request failed (#{response.status}): #{error_message}", error_code: error_code, timestamp: timestamp)
132
138
  end
133
139
  end
134
140
 
@@ -160,7 +166,17 @@ module Mysigner
160
166
  end
161
167
 
162
168
  # Custom errors
163
- class ClientError < StandardError; end
169
+ class ClientError < StandardError
170
+ attr_reader :error_code, :suggestion, :details, :timestamp
171
+
172
+ def initialize(message, error_code: nil, suggestion: nil, details: nil, timestamp: nil)
173
+ super(message)
174
+ @error_code = error_code
175
+ @suggestion = suggestion
176
+ @details = details
177
+ @timestamp = timestamp
178
+ end
179
+ end
164
180
  class ConnectionError < ClientError; end
165
181
  class TimeoutError < ClientError; end
166
182
  class UnauthorizedError < ClientError; end
@@ -169,11 +185,8 @@ module Mysigner
169
185
  class ServerError < ClientError; end
170
186
 
171
187
  class ValidationError < ClientError
172
- attr_reader :details
173
-
174
- def initialize(message, details = nil)
175
- super(message)
176
- @details = details
188
+ def initialize(message, details = nil, suggestion: nil, error_code: nil, timestamp: nil)
189
+ super(message, error_code: error_code, suggestion: suggestion, details: details, timestamp: timestamp)
177
190
  end
178
191
  end
179
192
 
@@ -71,7 +71,7 @@ module Mysigner
71
71
  if should_submit
72
72
  submit_for_review(
73
73
  version_id: version['id'],
74
- version_string: version['versionString'],
74
+ version_string: version['version_string'],
75
75
  metadata: metadata,
76
76
  overrides: metadata_overrides
77
77
  )
@@ -202,12 +202,12 @@ module Mysigner
202
202
 
203
203
  def ensure_app_store_version(app_id:, metadata:, overrides: {})
204
204
  desired_version = overrides['version_string'] || metadata['version_string'] || metadata['version']
205
- desired_version ||= metadata.dig('localizations', 0, 'versionString')
205
+ desired_version ||= metadata.dig('localizations', 0, 'version_string')
206
206
 
207
207
  current_version = fetch_editable_version(app_id)
208
208
 
209
209
  if current_version && version_matches?(current_version, desired_version)
210
- puts "✓ Reusing existing App Store version #{current_version['versionString']}"
210
+ puts "✓ Reusing existing App Store version #{current_version['version_string']}"
211
211
  update_version(current_version['id'], metadata, overrides)
212
212
  current_version
213
213
  else
@@ -234,7 +234,7 @@ module Mysigner
234
234
  normalized = desired.to_s.strip
235
235
  return false if normalized.empty?
236
236
 
237
- version['versionString'] == normalized || version.dig('attributes', 'versionString') == normalized
237
+ version['version_string'] == normalized || version.dig('attributes', 'versionString') == normalized
238
238
  end
239
239
 
240
240
  def build_default_version
@@ -247,8 +247,7 @@ module Mysigner
247
247
  app_id: app_id,
248
248
  version_string: version_string,
249
249
  release_type: determine_release_type(metadata, overrides),
250
- earliest_release_date: determine_earliest_release_date(metadata, overrides),
251
- attributes: extract_version_attributes(metadata, overrides)
250
+ earliest_release_date: determine_earliest_release_date(metadata, overrides)
252
251
  }.compact
253
252
  }
254
253
 
@@ -273,8 +272,9 @@ module Mysigner
273
272
  def update_version(version_id, metadata, overrides)
274
273
  payload = {
275
274
  app_store_version: {
276
- attributes: extract_version_attributes(metadata, overrides)
277
- }
275
+ release_type: determine_release_type(metadata, overrides),
276
+ earliest_release_date: determine_earliest_release_date(metadata, overrides)
277
+ }.compact
278
278
  }
279
279
 
280
280
  @client.patch(
@@ -301,18 +301,6 @@ module Mysigner
301
301
  result
302
302
  end
303
303
 
304
- def extract_version_attributes(metadata, overrides)
305
- localizations = overrides['localizations'] || metadata['localizations'] || []
306
- {
307
- whats_new: overrides['whats_new'] || metadata['whats_new'],
308
- support_url: overrides['support_url'] || metadata['support_url'],
309
- marketing_url: overrides['marketing_url'] || metadata['marketing_url'],
310
- privacy_policy_url: overrides['privacy_policy_url'] || metadata['privacy_policy_url'],
311
- phased_release: overrides.fetch('phased_release', metadata['phased_release']),
312
- localizations: localizations
313
- }.compact
314
- end
315
-
316
304
  def attach_build_to_version(version_id:, build_id:)
317
305
  @client.post(
318
306
  "/api/v1/organizations/#{@organization_id}/app_store_versions/#{version_id}/build",
@@ -1,3 +1,3 @@
1
1
  module Mysigner
2
- VERSION = "0.1.0"
2
+ VERSION = "0.1.1"
3
3
  end
data/mysigner.gemspec CHANGED
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
11
11
 
12
12
  spec.summary = %q{CLI tool for iOS and Android code signing automation via My Signer API}
13
13
  spec.description = %q{Command-line interface for managing iOS certificates, devices, provisioning profiles, and Android keystores. Build, sign, and upload to App Store Connect and Google Play with simple commands like 'mysigner ship testflight' and 'mysigner ship internal --platform android'.}
14
- spec.homepage = "https://github.com/jurgenleka/my-signer-cli"
14
+ spec.homepage = "https://mysigner.dev"
15
15
  spec.license = "Apache-2.0"
16
16
 
17
17
  spec.required_ruby_version = ">= 3.2.0"
@@ -22,8 +22,8 @@ Gem::Specification.new do |spec|
22
22
  spec.metadata["allowed_push_host"] = "https://rubygems.org"
23
23
 
24
24
  spec.metadata["homepage_uri"] = spec.homepage
25
- spec.metadata["source_code_uri"] = "https://github.com/jurgenleka/my-signer-cli"
26
- spec.metadata["changelog_uri"] = "https://github.com/jurgenleka/my-signer-cli/blob/main/CHANGELOG.md"
25
+ spec.metadata["changelog_uri"] = "https://mysigner.dev/docs/changelog"
26
+ spec.metadata["documentation_uri"] = "https://mysigner.dev/docs/commands"
27
27
  else
28
28
  raise "RubyGems 2.0 or newer is required to protect against " \
29
29
  "public gem pushes."
@@ -45,7 +45,7 @@ Gem::Specification.new do |spec|
45
45
  \e[35miOS:\e[0m mysigner ship testflight
46
46
  \e[35mAndroid:\e[0m mysigner ship internal --platform android
47
47
 
48
- \e[36mNeed docs?\e[0m https://github.com/jurgenleka/my-signer-cli
48
+ \e[36mDocs:\e[0m https://mysigner.dev/docs/commands
49
49
  MSG
50
50
 
51
51
  # Specify which files should be added to the gem when it is released.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mysigner
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jurgen Leka
@@ -248,14 +248,14 @@ files:
248
248
  - lib/mysigner/version.rb
249
249
  - mysigner.gemspec
250
250
  - test_manual.rb
251
- homepage: https://github.com/jurgenleka/my-signer-cli
251
+ homepage: https://mysigner.dev
252
252
  licenses:
253
253
  - Apache-2.0
254
254
  metadata:
255
255
  allowed_push_host: https://rubygems.org
256
- homepage_uri: https://github.com/jurgenleka/my-signer-cli
257
- source_code_uri: https://github.com/jurgenleka/my-signer-cli
258
- changelog_uri: https://github.com/jurgenleka/my-signer-cli/blob/main/CHANGELOG.md
256
+ homepage_uri: https://mysigner.dev
257
+ changelog_uri: https://mysigner.dev/docs/changelog
258
+ documentation_uri: https://mysigner.dev/docs/commands
259
259
  post_install_message: "\e[36m╔══════════════════════════════════════════════════════════════╗\e[0m\n\e[36m║
260
260
  \e[1m\U0001F680 Welcome to My Signer CLI\e[0m\e[36m ║\e[0m\n\e[36m╚══════════════════════════════════════════════════════════════╝\e[0m\n\n\e[32m✓\e[0m
261
261
  \ You're ready to automate iOS & Android code signing.\n\n\e[35mNext steps:\e[0m\n
@@ -264,8 +264,8 @@ post_install_message: "\e[36m╔════════════════
264
264
  if you already have a token\n • \e[33mRun\e[0m \e[1m`mysigner doctor`\e[0m –
265
265
  Validate your development environment\n • \e[33mRun\e[0m \e[1m`mysigner help`\e[0m
266
266
  \ – Explore every command in the toolbox\n\n\e[35miOS:\e[0m mysigner
267
- ship testflight\n\e[35mAndroid:\e[0m mysigner ship internal --platform android\n\n\e[36mNeed
268
- docs?\e[0m https://github.com/jurgenleka/my-signer-cli\n"
267
+ ship testflight\n\e[35mAndroid:\e[0m mysigner ship internal --platform android\n\n\e[36mDocs:\e[0m
268
+ https://mysigner.dev/docs/commands\n"
269
269
  rdoc_options: []
270
270
  require_paths:
271
271
  - lib