mysigner 0.3.1 → 0.3.2
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/CHANGELOG.md +19 -0
- data/Gemfile.lock +1 -1
- data/README.md +16 -0
- data/exe/mysigner +36 -1
- data/lib/mysigner/build/android_executor.rb +101 -29
- data/lib/mysigner/build/android_parser.rb +2 -2
- data/lib/mysigner/build/detector.rb +122 -53
- data/lib/mysigner/cli/auth_commands.rb +109 -6
- data/lib/mysigner/cli/build_commands.rb +24 -9
- data/lib/mysigner/cli/concerns/error_handlers.rb +9 -12
- data/lib/mysigner/cli/concerns/helpers.rb +37 -0
- data/lib/mysigner/cli/diagnostic_commands.rb +67 -29
- data/lib/mysigner/cli/resource_commands.rb +72 -27
- data/lib/mysigner/cli/validate_commands.rb +16 -1
- data/lib/mysigner/config.rb +14 -2
- data/lib/mysigner/signing/gradle_signing_injector.rb +14 -0
- data/lib/mysigner/version.rb +1 -1
- data/mysigner.gemspec +13 -8
- metadata +11 -10
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'stringio' # discover_local_*_silently use StringIO as a non-tty stderr proxy
|
|
4
|
+
|
|
3
5
|
module Mysigner
|
|
4
6
|
class CLI < Thor
|
|
5
7
|
module AuthCommands
|
|
@@ -28,6 +30,10 @@ module Mysigner
|
|
|
28
30
|
|
|
29
31
|
New user? Run 'mysigner onboard' for step-by-step guidance.
|
|
30
32
|
|
|
33
|
+
No My Signer account? You don't need one to sign and ship — run
|
|
34
|
+
'mysigner --local-only onboard' to use your own Apple/Google
|
|
35
|
+
credentials, kept on this machine.
|
|
36
|
+
|
|
31
37
|
Your credentials will be stored securely in ~/.mysigner/config.yml
|
|
32
38
|
|
|
33
39
|
Note: API tokens are organization-specific. This token will only
|
|
@@ -232,6 +238,26 @@ module Mysigner
|
|
|
232
238
|
|
|
233
239
|
# Check if already configured
|
|
234
240
|
config = Config.new
|
|
241
|
+
|
|
242
|
+
# Fresh user, interactive: offer the two modes up front so a newcomer
|
|
243
|
+
# doesn't assume a My Signer website account is mandatory.
|
|
244
|
+
if $stdin.tty? && !(config.exists? && config.current_organization_id)
|
|
245
|
+
say 'How do you want to use My Signer?', :cyan
|
|
246
|
+
say ''
|
|
247
|
+
say ' 1. With a free My Signer account', :white
|
|
248
|
+
say ' (keys stored on the server; sync across machines & team)'
|
|
249
|
+
say ' 2. Local-only — no account', :white
|
|
250
|
+
say ' (your Apple/Google keys stay on THIS machine; nothing sent to a server)'
|
|
251
|
+
say ''
|
|
252
|
+
say 'Not sure? Choose 2 (local-only).', :yellow
|
|
253
|
+
say ''
|
|
254
|
+
if ask('Select (1-2):', limited_to: %w[1 2]) == '2'
|
|
255
|
+
emit_local_only_banner
|
|
256
|
+
return onboard_local_only
|
|
257
|
+
end
|
|
258
|
+
say ''
|
|
259
|
+
end
|
|
260
|
+
|
|
235
261
|
if config.exists? && config.api_token && config.current_organization_id
|
|
236
262
|
say "✓ You're already logged in!", :green
|
|
237
263
|
say ''
|
|
@@ -540,6 +566,19 @@ module Mysigner
|
|
|
540
566
|
end
|
|
541
567
|
end
|
|
542
568
|
|
|
569
|
+
# Android: offer Google Play setup too. Previously onboard only
|
|
570
|
+
# ever configured App Store Connect, leaving Android users without
|
|
571
|
+
# a guided path even though setup_google_play_credentials existed.
|
|
572
|
+
# Only prompt interactively — in CI/non-tty contexts onboarding is
|
|
573
|
+
# driven by env vars/flags, not the wizard.
|
|
574
|
+
gp_configured = false
|
|
575
|
+
if $stdin.tty? && yes_with_default?('Set up Google Play (Android) credentials now?', :cyan)
|
|
576
|
+
say ''
|
|
577
|
+
gp_configured = setup_google_play_credentials(client, config, org_id)
|
|
578
|
+
elsif $stdin.tty?
|
|
579
|
+
say '⏭️ Skipped Google Play setup (run `mysigner onboard` again anytime)', :yellow
|
|
580
|
+
end
|
|
581
|
+
|
|
543
582
|
say ''
|
|
544
583
|
say '=' * 80, :green
|
|
545
584
|
say '🎉 Setup Complete!', :green
|
|
@@ -564,6 +603,13 @@ module Mysigner
|
|
|
564
603
|
say " Run 'mysigner doctor' to set it up", :yellow
|
|
565
604
|
end
|
|
566
605
|
|
|
606
|
+
if gp_configured
|
|
607
|
+
say '✓ Google Play: Configured', :green
|
|
608
|
+
else
|
|
609
|
+
say '⚠️ Google Play: Not configured', :yellow
|
|
610
|
+
say " Run 'mysigner onboard' to set it up", :yellow
|
|
611
|
+
end
|
|
612
|
+
|
|
567
613
|
say ''
|
|
568
614
|
say '🔒 Security Note:', :yellow
|
|
569
615
|
say " Your token is organization-specific. Use 'mysigner switch'", :yellow
|
|
@@ -1027,9 +1073,12 @@ module Mysigner
|
|
|
1027
1073
|
def config(action = nil, *args)
|
|
1028
1074
|
return config_set(*args) if action == 'set'
|
|
1029
1075
|
|
|
1030
|
-
|
|
1076
|
+
# `show` is the documented alias for the bare display (README +
|
|
1077
|
+
# `config [ACTION]` help). Treat nil/`show` as display; anything
|
|
1078
|
+
# else is a genuine typo.
|
|
1079
|
+
if action && !%w[show].include?(action)
|
|
1031
1080
|
error "Unknown config action: #{action}"
|
|
1032
|
-
say 'Did you mean: `mysigner config set <key> <value>`?', :yellow
|
|
1081
|
+
say 'Did you mean: `mysigner config show` or `mysigner config set <key> <value>`?', :yellow
|
|
1033
1082
|
exit 1
|
|
1034
1083
|
end
|
|
1035
1084
|
|
|
@@ -1679,23 +1728,35 @@ module Mysigner
|
|
|
1679
1728
|
say ''
|
|
1680
1729
|
stored_play = collect_local_google_play_credential
|
|
1681
1730
|
end
|
|
1731
|
+
say ''
|
|
1732
|
+
|
|
1733
|
+
# Android signing keystore (the actual signing key). Stored locally
|
|
1734
|
+
# in the exact envelope CredentialResolver#resolve_android_keystore
|
|
1735
|
+
# expects, so `ship --platform android --local-only` can sign offline.
|
|
1736
|
+
stored_keystore = []
|
|
1737
|
+
if $stdin.tty? && yes_with_default?('Set up an Android signing keystore now?', :cyan)
|
|
1738
|
+
say ''
|
|
1739
|
+
stored_keystore = collect_local_keystore_credential
|
|
1740
|
+
end
|
|
1682
1741
|
|
|
1683
1742
|
say ''
|
|
1684
1743
|
say '=' * 80, :green
|
|
1685
1744
|
say '✓ Local-only setup complete.', :green
|
|
1686
1745
|
say '=' * 80, :green
|
|
1687
1746
|
say ''
|
|
1688
|
-
if stored_asc.empty? && stored_play.empty?
|
|
1747
|
+
if stored_asc.empty? && stored_play.empty? && stored_keystore.empty?
|
|
1689
1748
|
say 'No credentials were stored.', :yellow
|
|
1690
1749
|
say "Re-run 'mysigner --local-only onboard' when you're ready.", :yellow
|
|
1691
1750
|
else
|
|
1692
1751
|
say 'Stored credentials:', :cyan
|
|
1693
|
-
stored_asc.each
|
|
1694
|
-
stored_play.each
|
|
1752
|
+
stored_asc.each { |id| say " • ASC key: #{id}" }
|
|
1753
|
+
stored_play.each { |id| say " • Google Play SA: #{id}" }
|
|
1754
|
+
stored_keystore.each { |id| say " • Android keystore: #{id}" }
|
|
1695
1755
|
say ''
|
|
1696
1756
|
say 'To ship:', :bold
|
|
1697
1757
|
say ' mysigner --local-only ship appstore' unless stored_asc.empty?
|
|
1698
1758
|
say ' mysigner --local-only ship play' unless stored_play.empty?
|
|
1759
|
+
say ' mysigner --local-only ship internal --platform android' unless stored_keystore.empty?
|
|
1699
1760
|
end
|
|
1700
1761
|
say ''
|
|
1701
1762
|
end
|
|
@@ -1721,8 +1782,14 @@ module Mysigner
|
|
|
1721
1782
|
key_id = ask('Enter your Key ID (e.g., ABC12345):').to_s.strip if key_id.nil? || key_id.empty?
|
|
1722
1783
|
raise_local_onboard_error!('Key ID cannot be empty') if key_id.empty?
|
|
1723
1784
|
|
|
1785
|
+
say 'Find your Issuer ID at https://appstoreconnect.apple.com/access/api', :cyan
|
|
1786
|
+
say '(the UUID shown at the top of the Keys page).', :cyan
|
|
1724
1787
|
issuer_id = ask('Enter your Issuer ID (UUID):').to_s.strip
|
|
1725
|
-
|
|
1788
|
+
if issuer_id.empty?
|
|
1789
|
+
raise_local_onboard_error!(
|
|
1790
|
+
"Issuer ID cannot be empty (it's the UUID at appstoreconnect.apple.com/access/api)"
|
|
1791
|
+
)
|
|
1792
|
+
end
|
|
1726
1793
|
|
|
1727
1794
|
# Storage shape matches mysigner-42's Option A: id == key_id,
|
|
1728
1795
|
# secret is a JSON envelope so AscJwtMinter can reconstruct
|
|
@@ -1753,6 +1820,42 @@ module Mysigner
|
|
|
1753
1820
|
[client_email]
|
|
1754
1821
|
end
|
|
1755
1822
|
|
|
1823
|
+
# Returns Array<String> of ids actually stored (empty on skip).
|
|
1824
|
+
# Persists the keystore bytes (base64) + passwords + alias under
|
|
1825
|
+
# LocalCredentials(kind: :android_keystore) in the exact envelope
|
|
1826
|
+
# CredentialResolver#resolve_android_keystore reads back.
|
|
1827
|
+
def collect_local_keystore_credential
|
|
1828
|
+
require 'base64'
|
|
1829
|
+
say '🔑 Android keystore (local-only)', :cyan
|
|
1830
|
+
say ''
|
|
1831
|
+
|
|
1832
|
+
ks_path = ask('Path to your keystore (.jks/.keystore):').to_s.strip.gsub(/^['"]|['"]$/, '')
|
|
1833
|
+
ks_path = File.expand_path(ks_path)
|
|
1834
|
+
raise_local_onboard_error!("Keystore file not found: #{ks_path}") unless File.exist?(ks_path)
|
|
1835
|
+
|
|
1836
|
+
# echo: false — keep secrets out of terminal scrollback, matching
|
|
1837
|
+
# every other secret prompt in this file.
|
|
1838
|
+
store_password = ask('Keystore password:', echo: false).to_s
|
|
1839
|
+
raise_local_onboard_error!('Keystore password cannot be empty') if store_password.empty?
|
|
1840
|
+
|
|
1841
|
+
key_alias = ask('Key alias:').to_s.strip
|
|
1842
|
+
raise_local_onboard_error!('Key alias cannot be empty') if key_alias.empty?
|
|
1843
|
+
|
|
1844
|
+
key_password = ask('Key password (press Enter to reuse the keystore password):', echo: false).to_s
|
|
1845
|
+
key_password = store_password if key_password.empty?
|
|
1846
|
+
|
|
1847
|
+
envelope = JSON.generate(
|
|
1848
|
+
'keystore_b64' => Base64.strict_encode64(File.binread(ks_path)),
|
|
1849
|
+
'keystore_password' => store_password,
|
|
1850
|
+
'key_alias' => key_alias,
|
|
1851
|
+
'key_password' => key_password
|
|
1852
|
+
)
|
|
1853
|
+
Mysigner::LocalCredentials.store(kind: :android_keystore, id: key_alias, secret: envelope)
|
|
1854
|
+
|
|
1855
|
+
say "✓ Android keystore stored locally (alias: #{key_alias}).", :green
|
|
1856
|
+
[key_alias]
|
|
1857
|
+
end
|
|
1858
|
+
|
|
1756
1859
|
# Verifies the file looks like an EC private key in the form
|
|
1757
1860
|
# AscJwtMinter requires. Fails loud — any malformed input raises
|
|
1758
1861
|
# before we touch the Keychain.
|
|
@@ -20,15 +20,18 @@ module Mysigner
|
|
|
20
20
|
long_desc <<~DESC
|
|
21
21
|
Build your project, sign it, and upload in one go.
|
|
22
22
|
|
|
23
|
-
iOS TARGETS
|
|
24
|
-
testflight :
|
|
25
|
-
appstore :
|
|
23
|
+
iOS TARGETS (requires macOS + Xcode)
|
|
24
|
+
testflight : Beta testing via TestFlight
|
|
25
|
+
appstore : Submit for public App Store release
|
|
26
26
|
|
|
27
|
-
ANDROID TARGETS
|
|
28
|
-
internal :
|
|
29
|
-
alpha :
|
|
30
|
-
beta :
|
|
31
|
-
production :
|
|
27
|
+
ANDROID TARGETS (works on macOS, Linux, and Windows)
|
|
28
|
+
internal : Fastest — up to 100 testers you list. NOT public.
|
|
29
|
+
alpha : Closed testing — an invite-only tester group.
|
|
30
|
+
beta : Open testing — anyone with your opt-in link.
|
|
31
|
+
production : PUBLIC — goes LIVE to everyone on the Google Play Store.
|
|
32
|
+
|
|
33
|
+
An AAB (Android App Bundle, .aab) is what Google Play requires — the
|
|
34
|
+
CLI builds and signs it for you (you don't upload an APK).
|
|
32
35
|
|
|
33
36
|
PLATFORM OPTIONS
|
|
34
37
|
--platform ios Force iOS build (auto-detected by default)
|
|
@@ -43,6 +46,14 @@ module Mysigner
|
|
|
43
46
|
--release-notes TEXT Release notes for Play Store
|
|
44
47
|
--package-name PKG Override the detected package name
|
|
45
48
|
|
|
49
|
+
LOCAL-ONLY (no My Signer account — use your OWN credentials)
|
|
50
|
+
Run `mysigner --local-only onboard` once for a guided setup, or pass:
|
|
51
|
+
iOS: --asc-key-path (the AuthKey_XXXX.p8 from
|
|
52
|
+
appstoreconnect.apple.com/access/api), --asc-key-id (the
|
|
53
|
+
XXXX in that filename), --asc-issuer-id (the UUID on that page)
|
|
54
|
+
Android: --keystore-path / --keystore-password / --key-alias and
|
|
55
|
+
--play-credentials (a Google Play service-account .json)
|
|
56
|
+
|
|
46
57
|
WORKFLOW
|
|
47
58
|
For iOS TestFlight:
|
|
48
59
|
mysigner ship testflight # Build → Upload → Done!
|
|
@@ -140,7 +151,8 @@ module Mysigner
|
|
|
140
151
|
return
|
|
141
152
|
end
|
|
142
153
|
|
|
143
|
-
# iOS flow continues below
|
|
154
|
+
# iOS flow continues below — requires macOS + Xcode.
|
|
155
|
+
require_macos!("ship #{target}")
|
|
144
156
|
|
|
145
157
|
is_appstore = (target == 'appstore')
|
|
146
158
|
|
|
@@ -1817,6 +1829,7 @@ module Mysigner
|
|
|
1817
1829
|
method_option :skip_extensions, type: :boolean, default: false,
|
|
1818
1830
|
desc: 'Skip extension targets (useful when extensions are not configured)'
|
|
1819
1831
|
def build
|
|
1832
|
+
require_macos!('build')
|
|
1820
1833
|
config = load_config
|
|
1821
1834
|
client = create_client(config)
|
|
1822
1835
|
|
|
@@ -2031,6 +2044,7 @@ module Mysigner
|
|
|
2031
2044
|
desc: 'Export method (appstore, adhoc, enterprise, development)'
|
|
2032
2045
|
method_option :output, type: :string, desc: 'Output directory for .ipa file'
|
|
2033
2046
|
def export(archive_path)
|
|
2047
|
+
require_macos!('export')
|
|
2034
2048
|
load_config
|
|
2035
2049
|
|
|
2036
2050
|
begin
|
|
@@ -2085,6 +2099,7 @@ module Mysigner
|
|
|
2085
2099
|
"Upload existing .ipa to TestFlight (advanced - most users should use 'ship')"
|
|
2086
2100
|
method_option :wait, type: :boolean, default: false, desc: 'Wait for processing to complete'
|
|
2087
2101
|
def upload(target, ipa_path)
|
|
2102
|
+
require_macos!('upload testflight')
|
|
2088
2103
|
unless target == 'testflight'
|
|
2089
2104
|
error "Only 'testflight' target is supported currently"
|
|
2090
2105
|
say 'Usage: mysigner upload testflight IPA_PATH', :yellow
|
|
@@ -84,19 +84,16 @@ module Mysigner
|
|
|
84
84
|
say ''
|
|
85
85
|
say 'To fix this:', :cyan
|
|
86
86
|
say ''
|
|
87
|
-
if api_url.include?('localhost')
|
|
88
|
-
say ' For
|
|
89
|
-
say ' 1.
|
|
90
|
-
say '
|
|
91
|
-
say ' bin/rails server'
|
|
92
|
-
say ''
|
|
93
|
-
say " 2. Verify it's accessible:"
|
|
94
|
-
say " curl #{api_url}/up"
|
|
87
|
+
if api_url.include?('localhost') && ENV['MYSIGNER_DEV']
|
|
88
|
+
say ' For My Signer backend development:', :bold
|
|
89
|
+
say ' 1. Start the Rails server: cd path/to/my-signer && bin/rails server'
|
|
90
|
+
say " 2. Verify it's accessible: curl #{api_url}/up"
|
|
95
91
|
else
|
|
96
|
-
say '
|
|
97
|
-
say ' 1. Check
|
|
98
|
-
say ' 2. Verify the
|
|
99
|
-
say
|
|
92
|
+
say ' To fix:', :bold
|
|
93
|
+
say ' 1. Check your internet connection'
|
|
94
|
+
say ' 2. Verify the API URL is correct: mysigner config show'
|
|
95
|
+
say " 3. Don't need a My Signer account? Run any command with --local-only"
|
|
96
|
+
say ' to sign with your own Apple/Google credentials.'
|
|
100
97
|
end
|
|
101
98
|
say ''
|
|
102
99
|
say ' Or set a custom API URL:', :bold
|
|
@@ -102,11 +102,27 @@ module Mysigner
|
|
|
102
102
|
say ' (auto-discovers ASC .p8 from ~/.appstoreconnect/private_keys/,', :yellow
|
|
103
103
|
say ' Google Play SA-JSON from GOOGLE_APPLICATION_CREDENTIALS / eas.json,', :yellow
|
|
104
104
|
say ' keystore from key.properties / eas.json — or set them via flags / env.)', :yellow
|
|
105
|
+
say ' Note: build / ship / sign work fully local; account commands', :yellow
|
|
106
|
+
say ' (orgs / switch / sync) still need a My Signer login.', :yellow
|
|
105
107
|
say ' See "Local-only mode" section in README.', :yellow
|
|
106
108
|
exit 1
|
|
107
109
|
end
|
|
108
110
|
|
|
109
111
|
config.load
|
|
112
|
+
|
|
113
|
+
# Surface an unreadable stored token as a clean re-login prompt here,
|
|
114
|
+
# at the auth gate, instead of letting the decrypt error explode later
|
|
115
|
+
# inside create_client / Config#display. (api_token decrypts lazily.)
|
|
116
|
+
begin
|
|
117
|
+
config.api_token
|
|
118
|
+
rescue Mysigner::ConfigError
|
|
119
|
+
error 'Your saved login is unreadable (encryption key changed or ' \
|
|
120
|
+
'config copied between machines).'
|
|
121
|
+
say "Run 'mysigner logout' then 'mysigner login' to re-authenticate.", :yellow
|
|
122
|
+
say 'Or run with --local-only to skip MySigner entirely.', :yellow
|
|
123
|
+
exit 1
|
|
124
|
+
end
|
|
125
|
+
|
|
110
126
|
config
|
|
111
127
|
end
|
|
112
128
|
|
|
@@ -150,6 +166,27 @@ module Mysigner
|
|
|
150
166
|
say "✗ Error: #{message}", :red
|
|
151
167
|
end
|
|
152
168
|
|
|
169
|
+
# True on macOS. iOS building/signing (Xcode, xcodebuild, the keychain)
|
|
170
|
+
# only works there; iOS-only commands call require_macos! to fail with a
|
|
171
|
+
# clear message instead of a raw "xcodebuild: not found" backtrace.
|
|
172
|
+
def macos?
|
|
173
|
+
!(RbConfig::CONFIG['host_os'] =~ /darwin/i).nil?
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Guard for iOS-only commands. On non-macOS, explain plainly and point
|
|
177
|
+
# the user at the Android path that DOES work cross-platform, then exit.
|
|
178
|
+
def require_macos!(command_label = 'This command')
|
|
179
|
+
return if macos?
|
|
180
|
+
|
|
181
|
+
error "#{command_label} requires macOS with Xcode."
|
|
182
|
+
say ''
|
|
183
|
+
say 'iOS building, signing, and uploading only work on a Mac (they use Xcode).', :yellow
|
|
184
|
+
say 'On Linux or Windows you can still build and ship Android:', :yellow
|
|
185
|
+
say ' mysigner ship internal --platform android', :cyan
|
|
186
|
+
say ' mysigner android build', :cyan
|
|
187
|
+
exit 1
|
|
188
|
+
end
|
|
189
|
+
|
|
153
190
|
# Local-only mode is active when any of, in precedence order:
|
|
154
191
|
# 1. --local-only / --no-local-only flag on this invocation
|
|
155
192
|
# 2. MYSIGNER_LOCAL_ONLY env var
|
|
@@ -18,8 +18,6 @@ module Mysigner
|
|
|
18
18
|
|
|
19
19
|
# Determine which platforms to check
|
|
20
20
|
platform_filter = options[:platform]&.downcase
|
|
21
|
-
check_ios = platform_filter.nil? || platform_filter == 'all' || platform_filter == 'ios'
|
|
22
|
-
check_android = platform_filter.nil? || platform_filter == 'all' || platform_filter == 'android'
|
|
23
21
|
|
|
24
22
|
if platform_filter && !%w[ios android all].include?(platform_filter)
|
|
25
23
|
error "Invalid platform: #{platform_filter}"
|
|
@@ -27,6 +25,18 @@ module Mysigner
|
|
|
27
25
|
exit 1
|
|
28
26
|
end
|
|
29
27
|
|
|
28
|
+
want_ios = platform_filter.nil? || platform_filter == 'all' || platform_filter == 'ios'
|
|
29
|
+
# iOS checks only mean something on macOS. On Linux/Windows, skip them
|
|
30
|
+
# with one info line instead of a wall of red "Xcode not found" issues —
|
|
31
|
+
# unless the user EXPLICITLY asked for --platform ios.
|
|
32
|
+
check_ios = want_ios && (macos? || platform_filter == 'ios')
|
|
33
|
+
check_android = platform_filter.nil? || platform_filter == 'all' || platform_filter == 'android'
|
|
34
|
+
|
|
35
|
+
if want_ios && !check_ios
|
|
36
|
+
say 'ℹ️ iOS checks skipped — iOS building requires macOS + Xcode.', :cyan
|
|
37
|
+
say ''
|
|
38
|
+
end
|
|
39
|
+
|
|
30
40
|
# Check 1: Xcode (iOS only)
|
|
31
41
|
if check_ios
|
|
32
42
|
say 'Checking Xcode...', :yellow
|
|
@@ -110,6 +120,12 @@ module Mysigner
|
|
|
110
120
|
say ' ✗ Token is invalid or expired', :red
|
|
111
121
|
issues << "Token authentication failed - run 'mysigner onboard' to re-authenticate"
|
|
112
122
|
client = nil
|
|
123
|
+
rescue Mysigner::ConfigError
|
|
124
|
+
# An undecryptable stored token is a LOCAL credential problem,
|
|
125
|
+
# not an API/network failure — say so and point at re-login.
|
|
126
|
+
say ' ✗ Saved credentials unreadable (encryption key changed or config corrupt)', :red
|
|
127
|
+
issues << "Stored login unreadable - run 'mysigner logout' then 'mysigner login'"
|
|
128
|
+
client = nil
|
|
113
129
|
rescue Mysigner::ConnectionError => e
|
|
114
130
|
say " ✗ Cannot connect to API: #{e.message}", :red
|
|
115
131
|
issues << 'API connection failed - check your network or API URL'
|
|
@@ -253,18 +269,24 @@ module Mysigner
|
|
|
253
269
|
end
|
|
254
270
|
say ''
|
|
255
271
|
|
|
256
|
-
# Check 8: Project Detection (
|
|
272
|
+
# Check 8: Project Detection (read-only — must NEVER trigger an
|
|
273
|
+
# expo prebuild from a diagnostic, hence allow_prebuild: false).
|
|
257
274
|
say 'Checking current directory...', :yellow
|
|
258
275
|
project_info = nil
|
|
259
276
|
begin
|
|
260
|
-
project_info = Build::Detector.detect
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
277
|
+
project_info = Build::Detector.detect(allow_prebuild: false)
|
|
278
|
+
if project_info[:needs_prebuild]
|
|
279
|
+
say ' ℹ️ Expo managed project detected (no native ios/ folder yet)', :cyan
|
|
280
|
+
say ' Generate it with: mysigner ship (or `npx expo prebuild`)', :cyan
|
|
281
|
+
else
|
|
282
|
+
framework = case project_info[:framework]
|
|
283
|
+
when :capacitor then 'Capacitor/Ionic'
|
|
284
|
+
when :react_native then 'React Native'
|
|
285
|
+
when :flutter then 'Flutter'
|
|
286
|
+
else 'Native iOS'
|
|
287
|
+
end
|
|
288
|
+
say " ✓ Found #{framework} project: #{File.basename(project_info[:path])}", :green
|
|
289
|
+
end
|
|
268
290
|
rescue StandardError
|
|
269
291
|
say ' ℹ️ No project detected in current directory', :cyan
|
|
270
292
|
end
|
|
@@ -516,6 +538,12 @@ module Mysigner
|
|
|
516
538
|
say ' ⚠️ JAVA_HOME not set', :yellow
|
|
517
539
|
end
|
|
518
540
|
end
|
|
541
|
+
elsif platform_filter == 'android'
|
|
542
|
+
# When the user explicitly asks to check Android, a missing JDK
|
|
543
|
+
# is a hard blocker, not an FYI — otherwise doctor green-lights an
|
|
544
|
+
# environment that cannot build.
|
|
545
|
+
say ' ✗ Java not found (required for Android)', :red
|
|
546
|
+
issues << 'Java (JDK) not found — required to build Android. Install JDK 17+ and set JAVA_HOME.'
|
|
519
547
|
else
|
|
520
548
|
say ' ℹ️ Java not found (required for Android)', :cyan
|
|
521
549
|
end
|
|
@@ -525,6 +553,9 @@ module Mysigner
|
|
|
525
553
|
if android_home && Dir.exist?(android_home)
|
|
526
554
|
say " ✓ Android SDK: #{android_home}", :green
|
|
527
555
|
android_available = true
|
|
556
|
+
elsif platform_filter == 'android'
|
|
557
|
+
say ' ✗ Android SDK not found (set ANDROID_HOME)', :red
|
|
558
|
+
issues << 'Android SDK not found — set ANDROID_HOME to your SDK location.'
|
|
528
559
|
else
|
|
529
560
|
say ' ℹ️ Android SDK not found (set ANDROID_HOME)', :cyan
|
|
530
561
|
end
|
|
@@ -581,25 +612,32 @@ module Mysigner
|
|
|
581
612
|
say ''
|
|
582
613
|
end
|
|
583
614
|
|
|
584
|
-
# Check 15: Android Project Detection
|
|
585
|
-
|
|
615
|
+
# Check 15: Android Project Detection (read-only — must NEVER
|
|
616
|
+
# trigger an expo prebuild from a diagnostic, hence allow_prebuild:
|
|
617
|
+
# false).
|
|
586
618
|
begin
|
|
587
|
-
android_project = Build::Detector.detect_android
|
|
588
|
-
framework = case android_project[:framework]
|
|
589
|
-
when :capacitor then 'Capacitor/Ionic'
|
|
590
|
-
when :react_native then 'React Native'
|
|
591
|
-
when :flutter then 'Flutter'
|
|
592
|
-
else 'Native Android'
|
|
593
|
-
end
|
|
619
|
+
android_project = Build::Detector.detect_android(allow_prebuild: false)
|
|
594
620
|
say 'Checking Android project...', :yellow
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
621
|
+
if android_project[:needs_prebuild]
|
|
622
|
+
say ' ℹ️ Expo managed project detected (no android/ folder yet)', :cyan
|
|
623
|
+
say ' Generate it with: mysigner android build', :cyan
|
|
624
|
+
say ' (needs Node >= 20.19.4 and `npm install` first)', :cyan
|
|
625
|
+
else
|
|
626
|
+
framework = case android_project[:framework]
|
|
627
|
+
when :capacitor then 'Capacitor/Ionic'
|
|
628
|
+
when :react_native then 'React Native'
|
|
629
|
+
when :flutter then 'Flutter'
|
|
630
|
+
else 'Native Android'
|
|
631
|
+
end
|
|
632
|
+
say " ✓ Found #{framework} Android project", :green
|
|
633
|
+
|
|
634
|
+
# Parse project details
|
|
635
|
+
require_relative '../build/android_parser'
|
|
636
|
+
parser = Build::AndroidParser.new(android_project)
|
|
637
|
+
say " Package: #{parser.application_id}", :cyan
|
|
638
|
+
say " Version: #{parser.version_name} (#{parser.version_code})", :cyan
|
|
639
|
+
say " Gradle wrapper: #{parser.gradle_wrapper_exists? ? '✓' : '✗'}", :cyan
|
|
640
|
+
end
|
|
603
641
|
say ''
|
|
604
642
|
rescue Build::Detector::NoProjectError
|
|
605
643
|
# Not an Android project, that's fine
|
|
@@ -617,7 +655,7 @@ module Mysigner
|
|
|
617
655
|
if issues.empty? && warnings.empty?
|
|
618
656
|
say "🎉 All checks passed! You're good to go!", :green
|
|
619
657
|
say ''
|
|
620
|
-
say 'Try: mysigner ship testflight', :cyan
|
|
658
|
+
say(platform_filter == 'android' ? 'Try: mysigner ship internal --platform android' : 'Try: mysigner ship testflight', :cyan)
|
|
621
659
|
elsif issues.empty?
|
|
622
660
|
say "⚠️ #{warnings.length} warning(s), but you're mostly good!", :yellow
|
|
623
661
|
say ''
|