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 +4 -4
- data/.DS_Store +0 -0
- data/Gemfile.lock +1 -1
- data/lib/mysigner/build/configurator.rb +1 -1
- data/lib/mysigner/cli/auth_commands.rb +3 -3
- data/lib/mysigner/cli/build_commands.rb +10 -10
- data/lib/mysigner/cli/diagnostic_commands.rb +4 -4
- data/lib/mysigner/cli/resource_commands.rb +208 -24
- data/lib/mysigner/client.rb +26 -13
- data/lib/mysigner/upload/app_store_automation.rb +8 -20
- data/lib/mysigner/version.rb +1 -1
- data/mysigner.gemspec +4 -4
- metadata +7 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f8cd24e9a29c341d204f6cc2d9c5cde99c6248b655bf004fa370daa78abc9cd1
|
|
4
|
+
data.tar.gz: 92e37eff120a9a3ca8325d40b720c2878339735a368fae5cbe917017682825d7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b81c29b7b84b9357d0419c9333d0f8b769e475366fb5d73d5baa278c7d37e0e5f2a788b2e742f9dff731a5df821de09f1b651498ec496520b7600988d60c22de
|
|
7
|
+
data.tar.gz: f035dbf8191e61be0f411a3ed8f8894f1cb725ed3b804f3dc6a33e9be466870af2a74cb150e1551b42a4e3862b6cd4feb9858c27d0a3e36fcea72a55a372df74
|
data/.DS_Store
CHANGED
|
Binary file
|
data/Gemfile.lock
CHANGED
|
@@ -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 "
|
|
15
|
-
say "
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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['
|
|
469
|
-
status_color = profile['
|
|
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['
|
|
474
|
-
say " Status: #{profile['
|
|
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
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
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
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
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"
|
data/lib/mysigner/client.rb
CHANGED
|
@@ -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,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
135
|
+
raise ServerError.new("Server error (#{response.status}): #{error_message}", error_code: error_code, timestamp: timestamp)
|
|
130
136
|
else
|
|
131
|
-
raise ClientError
|
|
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
|
|
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
|
-
|
|
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['
|
|
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, '
|
|
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['
|
|
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['
|
|
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
|
-
|
|
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",
|
data/lib/mysigner/version.rb
CHANGED
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://
|
|
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["
|
|
26
|
-
spec.metadata["
|
|
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[
|
|
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.
|
|
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://
|
|
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://
|
|
257
|
-
|
|
258
|
-
|
|
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[
|
|
268
|
-
|
|
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
|