mysigner 0.1.1 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.githooks/pre-commit +15 -0
- data/.githooks/pre-push +21 -0
- data/.github/workflows/ci.yml +29 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +55 -0
- data/.rubocop_todo.yml +112 -0
- data/CHANGELOG.md +96 -0
- data/Gemfile +5 -3
- data/Gemfile.lock +38 -8
- data/README.md +87 -17
- data/Rakefile +5 -3
- data/bin/console +4 -3
- data/bin/setup +3 -0
- data/exe/mysigner +2 -1
- data/lib/mysigner/build/android_executor.rb +46 -52
- data/lib/mysigner/build/android_parser.rb +33 -40
- data/lib/mysigner/build/configurator.rb +17 -16
- data/lib/mysigner/build/detector.rb +39 -50
- data/lib/mysigner/build/error_analyzer.rb +70 -68
- data/lib/mysigner/build/executor.rb +30 -37
- data/lib/mysigner/build/parser.rb +18 -18
- data/lib/mysigner/cli/auth_commands.rb +735 -752
- data/lib/mysigner/cli/build_commands.rb +697 -721
- data/lib/mysigner/cli/concerns/actionable_suggestions.rb +208 -154
- data/lib/mysigner/cli/concerns/api_helpers.rb +46 -54
- data/lib/mysigner/cli/concerns/error_handlers.rb +247 -237
- data/lib/mysigner/cli/concerns/helpers.rb +12 -1
- data/lib/mysigner/cli/diagnostic_commands.rb +659 -635
- data/lib/mysigner/cli/resource_commands.rb +1266 -822
- data/lib/mysigner/cli/validate_commands.rb +161 -0
- data/lib/mysigner/cli.rb +5 -1
- data/lib/mysigner/client.rb +27 -19
- data/lib/mysigner/config.rb +93 -56
- data/lib/mysigner/export/exporter.rb +32 -36
- data/lib/mysigner/signing/certificate_checker.rb +18 -23
- data/lib/mysigner/signing/keystore_manager.rb +34 -39
- data/lib/mysigner/signing/validator.rb +38 -40
- data/lib/mysigner/signing/wizard.rb +329 -342
- data/lib/mysigner/upload/app_store_automation.rb +51 -49
- data/lib/mysigner/upload/app_store_submission.rb +87 -92
- data/lib/mysigner/upload/play_store_uploader.rb +98 -115
- data/lib/mysigner/upload/uploader.rb +101 -109
- data/lib/mysigner/version.rb +3 -1
- data/lib/mysigner.rb +13 -11
- data/mysigner.gemspec +36 -33
- data/test_manual.rb +37 -36
- metadata +38 -16
|
@@ -1,18 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'English'
|
|
1
4
|
module Mysigner
|
|
2
5
|
class CLI < Thor
|
|
3
6
|
module DiagnosticCommands
|
|
4
7
|
def self.included(base)
|
|
5
8
|
base.class_eval do
|
|
6
|
-
desc
|
|
9
|
+
desc 'doctor', '🩺 Run health check and diagnose setup issues (run this if stuck)'
|
|
7
10
|
method_option :platform, type: :string, desc: 'Check specific platform only: ios, android, or all (default)'
|
|
8
11
|
def doctor
|
|
9
|
-
say
|
|
10
|
-
say
|
|
11
|
-
say
|
|
12
|
-
|
|
12
|
+
say '🩺 My Signer Health Check', :cyan
|
|
13
|
+
say '=' * 80, :cyan
|
|
14
|
+
say ''
|
|
15
|
+
|
|
13
16
|
issues = []
|
|
14
17
|
warnings = []
|
|
15
|
-
|
|
18
|
+
|
|
16
19
|
# Determine which platforms to check
|
|
17
20
|
platform_filter = options[:platform]&.downcase
|
|
18
21
|
check_ios = platform_filter.nil? || platform_filter == 'all' || platform_filter == 'ios'
|
|
@@ -20,594 +23,614 @@ module Mysigner
|
|
|
20
23
|
|
|
21
24
|
if platform_filter && !%w[ios android all].include?(platform_filter)
|
|
22
25
|
error "Invalid platform: #{platform_filter}"
|
|
23
|
-
say
|
|
26
|
+
say 'Valid options: ios, android, all', :yellow
|
|
24
27
|
exit 1
|
|
25
28
|
end
|
|
26
29
|
|
|
27
30
|
# Check 1: Xcode (iOS only)
|
|
28
31
|
if check_ios
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
issues << "Xcode is not installed or not in PATH"
|
|
36
|
-
end
|
|
37
|
-
say ""
|
|
38
|
-
|
|
39
|
-
# Check 2: Command Line Tools
|
|
40
|
-
say "Checking Command Line Tools...", :yellow
|
|
41
|
-
if system('xcode-select -p > /dev/null 2>&1')
|
|
42
|
-
say " ✓ Command Line Tools installed", :green
|
|
43
|
-
else
|
|
44
|
-
say " ✗ Command Line Tools not found", :red
|
|
45
|
-
issues << "Install with: xcode-select --install"
|
|
46
|
-
end
|
|
47
|
-
say ""
|
|
48
|
-
|
|
49
|
-
# Check 3: xcrun altool
|
|
50
|
-
say "Checking upload tools...", :yellow
|
|
51
|
-
if system('xcrun --find altool > /dev/null 2>&1')
|
|
52
|
-
say " ✓ xcrun altool available", :green
|
|
53
|
-
else
|
|
54
|
-
say " ⚠️ xcrun altool not found", :yellow
|
|
55
|
-
warnings << "altool not available (upload may fail)"
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
# Check for iTMSTransporter
|
|
59
|
-
transporter_paths = [
|
|
60
|
-
'/Applications/Xcode.app/Contents/Developer/usr/bin/iTMSTransporter',
|
|
61
|
-
'/Applications/Transporter.app/Contents/itms/bin/iTMSTransporter'
|
|
62
|
-
]
|
|
63
|
-
transporter_found = transporter_paths.any? { |path| File.exist?(path) }
|
|
64
|
-
|
|
65
|
-
if transporter_found
|
|
66
|
-
say " ✓ iTMSTransporter available (fallback)", :green
|
|
67
|
-
else
|
|
68
|
-
say " ⚠️ iTMSTransporter not found (optional)", :yellow
|
|
69
|
-
end
|
|
70
|
-
say ""
|
|
71
|
-
|
|
72
|
-
# Check 4: My Signer Configuration
|
|
73
|
-
say "Checking My Signer configuration...", :yellow
|
|
74
|
-
config = Config.new
|
|
75
|
-
client = nil
|
|
76
|
-
org_data = nil
|
|
77
|
-
|
|
78
|
-
if config.exists?
|
|
79
|
-
config.load
|
|
80
|
-
say " ✓ Logged in", :green
|
|
81
|
-
|
|
82
|
-
begin
|
|
83
|
-
client = Client.new(api_url: config.api_url, api_token: config.api_token, user_email: config.user_email)
|
|
84
|
-
client.test_connection
|
|
85
|
-
say " ✓ API connection working", :green
|
|
86
|
-
|
|
87
|
-
# Get organization details
|
|
88
|
-
if config.current_organization_id
|
|
89
|
-
org_response = client.get("/api/v1/organizations/#{config.current_organization_id}")
|
|
90
|
-
org_data = org_response[:data]
|
|
32
|
+
say 'Checking Xcode...', :yellow
|
|
33
|
+
if system('which xcodebuild > /dev/null 2>&1')
|
|
34
|
+
xcode_version = begin
|
|
35
|
+
`xcodebuild -version`.lines.first.strip
|
|
36
|
+
rescue StandardError
|
|
37
|
+
'Unknown'
|
|
91
38
|
end
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
rescue Mysigner::ConnectionError => e
|
|
97
|
-
say " ✗ Cannot connect to API: #{e.message}", :red
|
|
98
|
-
issues << "API connection failed - check your network or API URL"
|
|
99
|
-
client = nil
|
|
100
|
-
rescue => e
|
|
101
|
-
say " ✗ API error: #{e.message}", :red
|
|
102
|
-
issues << "API connection failed - check your configuration"
|
|
103
|
-
client = nil
|
|
39
|
+
say " ✓ Xcode installed: #{xcode_version}", :green
|
|
40
|
+
else
|
|
41
|
+
say ' ✗ Xcode not found', :red
|
|
42
|
+
issues << 'Xcode is not installed or not in PATH'
|
|
104
43
|
end
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
# Check 4a: Signing Identity in Keychain (CRITICAL)
|
|
112
|
-
if client && org_data && org_data['app_store_connect_team_id']
|
|
113
|
-
say "Checking signing identity for team...", :yellow
|
|
114
|
-
team_id = org_data['app_store_connect_team_id']
|
|
115
|
-
|
|
116
|
-
# Check if signing identities exist in keychain for this team
|
|
117
|
-
identities = `security find-identity -v -p codesigning 2>/dev/null | grep -i "#{team_id}"`
|
|
118
|
-
has_identity = $?.success? && !identities.strip.empty?
|
|
119
|
-
|
|
120
|
-
if has_identity
|
|
121
|
-
say " ✓ Signing identity found for team #{team_id}", :green
|
|
44
|
+
say ''
|
|
45
|
+
|
|
46
|
+
# Check 2: Command Line Tools
|
|
47
|
+
say 'Checking Command Line Tools...', :yellow
|
|
48
|
+
if system('xcode-select -p > /dev/null 2>&1')
|
|
49
|
+
say ' ✓ Command Line Tools installed', :green
|
|
122
50
|
else
|
|
123
|
-
say
|
|
124
|
-
|
|
125
|
-
say " CRITICAL: You need to sign into Xcode and download certificates:", :red
|
|
126
|
-
say " 1. Open Xcode → Settings → Accounts", :yellow
|
|
127
|
-
say " 2. Verify you're signed in with your Apple ID", :yellow
|
|
128
|
-
say " 3. Select team '#{team_id}'", :yellow
|
|
129
|
-
say " 4. Click 'Download Manual Profiles' (or 'Manage Certificates')", :yellow
|
|
130
|
-
say " 5. The certificate should appear in keychain", :yellow
|
|
131
|
-
say ""
|
|
132
|
-
issues << "No signing identity for team #{team_id} in keychain"
|
|
51
|
+
say ' ✗ Command Line Tools not found', :red
|
|
52
|
+
issues << 'Install with: xcode-select --install'
|
|
133
53
|
end
|
|
134
|
-
say
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
54
|
+
say ''
|
|
55
|
+
|
|
56
|
+
# Check 3: xcrun altool
|
|
57
|
+
say 'Checking upload tools...', :yellow
|
|
58
|
+
if system('xcrun --find altool > /dev/null 2>&1')
|
|
59
|
+
say ' ✓ xcrun altool available', :green
|
|
60
|
+
else
|
|
61
|
+
say ' ⚠️ xcrun altool not found', :yellow
|
|
62
|
+
warnings << 'altool not available (upload may fail)'
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Check for iTMSTransporter
|
|
66
|
+
transporter_paths = [
|
|
67
|
+
'/Applications/Xcode.app/Contents/Developer/usr/bin/iTMSTransporter',
|
|
68
|
+
'/Applications/Transporter.app/Contents/itms/bin/iTMSTransporter'
|
|
69
|
+
]
|
|
70
|
+
transporter_found = transporter_paths.any? { |path| File.exist?(path) }
|
|
71
|
+
|
|
72
|
+
if transporter_found
|
|
73
|
+
say ' ✓ iTMSTransporter available (fallback)', :green
|
|
74
|
+
else
|
|
75
|
+
say ' ⚠️ iTMSTransporter not found (optional)', :yellow
|
|
76
|
+
end
|
|
77
|
+
say ''
|
|
78
|
+
|
|
79
|
+
# Check 4: My Signer Configuration
|
|
80
|
+
say 'Checking My Signer configuration...', :yellow
|
|
81
|
+
client = nil
|
|
82
|
+
org_data = nil
|
|
83
|
+
|
|
84
|
+
config = if Config.env_configured?
|
|
85
|
+
Config.from_env
|
|
86
|
+
else
|
|
87
|
+
Config.new
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
if config.from_env? || config.exists?
|
|
91
|
+
config.load unless config.from_env?
|
|
92
|
+
say config.from_env? ? ' ✓ Configured via environment variables' : ' ✓ Logged in', :green
|
|
93
|
+
|
|
94
|
+
begin
|
|
95
|
+
client = Client.new(api_url: config.api_url, api_token: config.api_token,
|
|
96
|
+
user_email: config.user_email)
|
|
97
|
+
client.test_connection
|
|
98
|
+
say ' ✓ API connection working', :green
|
|
99
|
+
|
|
100
|
+
# Get organization details
|
|
101
|
+
if config.current_organization_id
|
|
162
102
|
org_response = client.get("/api/v1/organizations/#{config.current_organization_id}")
|
|
163
103
|
org_data = org_response[:data]
|
|
164
|
-
else
|
|
165
|
-
say ""
|
|
166
|
-
issues << "App Store Connect setup incomplete - run 'mysigner onboard' to try again"
|
|
167
104
|
end
|
|
168
|
-
|
|
169
|
-
|
|
105
|
+
rescue Mysigner::UnauthorizedError
|
|
106
|
+
say ' ✗ Token is invalid or expired', :red
|
|
107
|
+
issues << "Token authentication failed - run 'mysigner onboard' to re-authenticate"
|
|
108
|
+
client = nil
|
|
109
|
+
rescue Mysigner::ConnectionError => e
|
|
110
|
+
say " ✗ Cannot connect to API: #{e.message}", :red
|
|
111
|
+
issues << 'API connection failed - check your network or API URL'
|
|
112
|
+
client = nil
|
|
113
|
+
rescue StandardError => e
|
|
114
|
+
say " ✗ API error: #{e.message}", :red
|
|
115
|
+
issues << 'API connection failed - check your configuration'
|
|
116
|
+
client = nil
|
|
170
117
|
end
|
|
171
|
-
elsif !creds_status['team_id_set']
|
|
172
|
-
say " ⚠️ Team ID not set", :yellow
|
|
173
|
-
warnings << "Team ID missing - may cause issues. Re-sync to extract it."
|
|
174
118
|
else
|
|
175
|
-
say
|
|
176
|
-
|
|
177
|
-
say " Team ID: #{org_data['app_store_connect_team_id']}", :cyan
|
|
178
|
-
end
|
|
119
|
+
say ' ✗ Not logged in', :red
|
|
120
|
+
issues << "Run 'mysigner onboard' to authenticate"
|
|
179
121
|
end
|
|
180
|
-
say
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
if license_check.include?("password") || license_check.include?("sudo")
|
|
194
|
-
say " ℹ️ Cannot check license (needs sudo)", :cyan
|
|
122
|
+
say ''
|
|
123
|
+
|
|
124
|
+
# Check 4a: Signing Identity in Keychain (CRITICAL)
|
|
125
|
+
if client && org_data && org_data['app_store_connect_team_id']
|
|
126
|
+
say 'Checking signing identity for team...', :yellow
|
|
127
|
+
team_id = org_data['app_store_connect_team_id']
|
|
128
|
+
|
|
129
|
+
# Check if signing identities exist in keychain for this team
|
|
130
|
+
identities = `security find-identity -v -p codesigning 2>/dev/null | grep -i "#{team_id}"`
|
|
131
|
+
has_identity = $CHILD_STATUS.success? && !identities.strip.empty?
|
|
132
|
+
|
|
133
|
+
if has_identity
|
|
134
|
+
say " ✓ Signing identity found for team #{team_id}", :green
|
|
195
135
|
else
|
|
196
|
-
say "
|
|
197
|
-
say
|
|
198
|
-
|
|
136
|
+
say " ✗ No signing identity for team #{team_id}", :red
|
|
137
|
+
say ''
|
|
138
|
+
say ' CRITICAL: You need to sign into Xcode and download certificates:', :red
|
|
139
|
+
say ' 1. Open Xcode → Settings → Accounts', :yellow
|
|
140
|
+
say " 2. Verify you're signed in with your Apple ID", :yellow
|
|
141
|
+
say " 3. Select team '#{team_id}'", :yellow
|
|
142
|
+
say " 4. Click 'Download Manual Profiles' (or 'Manage Certificates')", :yellow
|
|
143
|
+
say ' 5. The certificate should appear in keychain', :yellow
|
|
144
|
+
say ''
|
|
145
|
+
issues << "No signing identity for team #{team_id} in keychain"
|
|
199
146
|
end
|
|
147
|
+
say ''
|
|
200
148
|
end
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
149
|
+
|
|
150
|
+
# Check 4b: App Store Connect Credentials (with auto-fix)
|
|
151
|
+
if client && org_data
|
|
152
|
+
say 'Checking App Store Connect credentials...', :yellow
|
|
153
|
+
creds_status = org_data['credentials_status'] || {}
|
|
154
|
+
|
|
155
|
+
if creds_status['needs_setup'] || !org_data['app_store_connect_configured']
|
|
156
|
+
say ' ✗ App Store Connect not configured', :red
|
|
157
|
+
say ''
|
|
158
|
+
|
|
159
|
+
if yes_with_default?('Would you like to set it up now?', :green)
|
|
160
|
+
say ''
|
|
161
|
+
# Call the setup helper (available from AuthCommands module)
|
|
162
|
+
if respond_to?(:setup_app_store_connect_credentials, true)
|
|
163
|
+
asc_configured = setup_app_store_connect_credentials(client, config,
|
|
164
|
+
config.current_organization_id)
|
|
165
|
+
else
|
|
166
|
+
say ' ✗ Setup helper not available', :red
|
|
167
|
+
say " Please run 'mysigner onboard' instead", :yellow
|
|
168
|
+
asc_configured = false
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
say ''
|
|
172
|
+
if asc_configured
|
|
173
|
+
say ' ✓ App Store Connect configured successfully!', :green
|
|
174
|
+
say ''
|
|
175
|
+
# Refresh org data
|
|
176
|
+
org_response = client.get("/api/v1/organizations/#{config.current_organization_id}")
|
|
177
|
+
org_data = org_response[:data]
|
|
178
|
+
else
|
|
179
|
+
issues << "App Store Connect setup incomplete - run 'mysigner onboard' to try again"
|
|
180
|
+
end
|
|
181
|
+
else
|
|
182
|
+
issues << "App Store Connect not configured - run 'mysigner onboard' to set it up"
|
|
183
|
+
end
|
|
184
|
+
elsif !creds_status['team_id_set']
|
|
185
|
+
say ' ⚠️ Team ID not set', :yellow
|
|
186
|
+
warnings << 'Team ID missing - may cause issues. Re-sync to extract it.'
|
|
218
187
|
else
|
|
219
|
-
say
|
|
188
|
+
say ' ✓ App Store Connect configured', :green
|
|
189
|
+
say " Team ID: #{org_data['app_store_connect_team_id']}", :cyan if org_data['app_store_connect_team_id']
|
|
220
190
|
end
|
|
221
|
-
|
|
222
|
-
say " ⚠️ Could not check disk space", :yellow
|
|
191
|
+
say ''
|
|
223
192
|
end
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
project_info = nil
|
|
244
|
-
begin
|
|
245
|
-
project_info = Build::Detector.detect
|
|
246
|
-
framework = case project_info[:framework]
|
|
247
|
-
when :capacitor then "Capacitor/Ionic"
|
|
248
|
-
when :react_native then "React Native"
|
|
249
|
-
when :flutter then "Flutter"
|
|
250
|
-
else "Native iOS"
|
|
193
|
+
|
|
194
|
+
# Check 5: Xcode License Agreement
|
|
195
|
+
say 'Checking Xcode license...', :yellow
|
|
196
|
+
begin
|
|
197
|
+
license_check = `sudo -n xcodebuild -checkFirstLaunchStatus 2>&1`
|
|
198
|
+
license_status = $CHILD_STATUS.success?
|
|
199
|
+
|
|
200
|
+
if license_status
|
|
201
|
+
say ' ✓ Xcode license accepted', :green
|
|
202
|
+
elsif license_check.include?('password') || license_check.include?('sudo')
|
|
203
|
+
# Check if it's a permission issue or actual license issue
|
|
204
|
+
say ' ℹ️ Cannot check license (needs sudo)', :cyan
|
|
205
|
+
else
|
|
206
|
+
say ' ⚠️ Xcode license may not be accepted', :yellow
|
|
207
|
+
say ' Run: sudo xcodebuild -license accept', :cyan
|
|
208
|
+
warnings << 'Xcode license may need acceptance'
|
|
209
|
+
end
|
|
210
|
+
rescue StandardError
|
|
211
|
+
say ' ℹ️ Could not check Xcode license', :cyan
|
|
251
212
|
end
|
|
252
|
-
say
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
213
|
+
say ''
|
|
214
|
+
|
|
215
|
+
# Check 6: Disk Space
|
|
216
|
+
say 'Checking disk space...', :yellow
|
|
217
|
+
begin
|
|
218
|
+
df_output = `df -h . 2>/dev/null | tail -1`.strip
|
|
219
|
+
if df_output =~ /(\d+)%/
|
|
220
|
+
usage = ::Regexp.last_match(1).to_i
|
|
221
|
+
if usage > 95
|
|
222
|
+
say " ✗ Critical: Disk space very low (#{usage}% used)", :red
|
|
223
|
+
issues << 'Free up disk space before building'
|
|
224
|
+
elsif usage > 90
|
|
225
|
+
say " ⚠️ Low disk space: #{usage}% used", :yellow
|
|
226
|
+
warnings << 'Low disk space may cause build failures'
|
|
227
|
+
else
|
|
228
|
+
say " ✓ Sufficient disk space: #{usage}% used", :green
|
|
229
|
+
end
|
|
230
|
+
else
|
|
231
|
+
say ' ⚠️ Could not check disk space', :yellow
|
|
232
|
+
end
|
|
233
|
+
rescue StandardError
|
|
234
|
+
say ' ⚠️ Could not check disk space', :yellow
|
|
271
235
|
end
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
say
|
|
236
|
+
say ''
|
|
237
|
+
|
|
238
|
+
# Check 7: Network Connectivity
|
|
239
|
+
say 'Checking network connectivity...', :yellow
|
|
240
|
+
begin
|
|
241
|
+
require 'socket'
|
|
242
|
+
Socket.tcp('apple.com', 443, connect_timeout: 5, &:close)
|
|
243
|
+
say ' ✓ Internet connection working', :green
|
|
244
|
+
rescue StandardError => e
|
|
245
|
+
say ' ✗ No internet connection', :red
|
|
246
|
+
issues << 'Cannot reach Apple servers - check your network connection'
|
|
280
247
|
end
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
248
|
+
say ''
|
|
249
|
+
|
|
250
|
+
# Check 8: Project Detection (if in a project directory)
|
|
251
|
+
say 'Checking current directory...', :yellow
|
|
252
|
+
project_info = nil
|
|
253
|
+
begin
|
|
254
|
+
project_info = Build::Detector.detect
|
|
255
|
+
framework = case project_info[:framework]
|
|
256
|
+
when :capacitor then 'Capacitor/Ionic'
|
|
257
|
+
when :react_native then 'React Native'
|
|
258
|
+
when :flutter then 'Flutter'
|
|
259
|
+
else 'Native iOS'
|
|
260
|
+
end
|
|
261
|
+
say " ✓ Found #{framework} project: #{File.basename(project_info[:path])}", :green
|
|
262
|
+
rescue StandardError
|
|
263
|
+
say ' ℹ️ No project detected in current directory', :cyan
|
|
289
264
|
end
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
265
|
+
say ''
|
|
266
|
+
|
|
267
|
+
# Check 9: Organization Resources Health (if logged in)
|
|
268
|
+
if client && org_data && org_data['app_store_connect_configured']
|
|
269
|
+
say 'Checking organization resources...', :yellow
|
|
270
|
+
|
|
271
|
+
stats = org_data['stats'] || {}
|
|
272
|
+
|
|
273
|
+
# Check certificates
|
|
274
|
+
certs_count = stats['certificates_count'] || 0
|
|
275
|
+
if certs_count.zero?
|
|
276
|
+
say ' ⚠️ No certificates synced', :yellow
|
|
277
|
+
warnings << 'Run sync in web dashboard to fetch certificates from Apple'
|
|
278
|
+
else
|
|
279
|
+
say " ✓ Certificates: #{certs_count}", :green
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
# Check devices
|
|
283
|
+
devices_count = stats['devices_count'] || 0
|
|
284
|
+
if devices_count.zero?
|
|
285
|
+
say ' ⚠️ No devices registered', :yellow
|
|
286
|
+
warnings << 'Add devices for development/adhoc builds'
|
|
287
|
+
else
|
|
288
|
+
say " ✓ Devices: #{devices_count}", :green
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# Check bundle IDs
|
|
292
|
+
bundle_ids_count = stats['bundle_ids_count'] || 0
|
|
293
|
+
if bundle_ids_count.zero?
|
|
294
|
+
say ' ⚠️ No bundle IDs synced', :yellow
|
|
295
|
+
say ' This is normal for new accounts', :cyan
|
|
296
|
+
else
|
|
297
|
+
say " ✓ Bundle IDs: #{bundle_ids_count}", :green
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# Check profiles
|
|
301
|
+
profiles_count = stats['profiles_count'] || 0
|
|
302
|
+
invalid_profiles = stats['invalid_profiles_count'] || 0
|
|
303
|
+
|
|
304
|
+
if profiles_count.zero?
|
|
305
|
+
say ' ⚠️ No provisioning profiles', :yellow
|
|
306
|
+
warnings << 'Create profiles for your projects'
|
|
307
|
+
elsif invalid_profiles.positive?
|
|
308
|
+
say " ✓ Profiles: #{profiles_count} (⚠️ #{invalid_profiles} invalid)", :yellow
|
|
309
|
+
warnings << "#{invalid_profiles} profile(s) invalid - may need regeneration"
|
|
310
|
+
else
|
|
311
|
+
say " ✓ Profiles: #{profiles_count}", :green
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
say ''
|
|
303
315
|
end
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
bundle_ids_response = client.get(
|
|
328
|
-
"/api/v1/organizations/#{config.current_organization_id}/bundle_ids",
|
|
329
|
-
params: { q: bundle_id }
|
|
330
|
-
)
|
|
331
|
-
|
|
332
|
-
bundle_id_exists = (bundle_ids_response[:data]['bundle_ids'] || []).any? do |bid|
|
|
333
|
-
bid['identifier'] == bundle_id
|
|
334
|
-
end
|
|
335
|
-
|
|
336
|
-
if !bundle_id_exists
|
|
337
|
-
say " ✗ Bundle ID '#{bundle_id}' not registered in App Store Connect", :red
|
|
338
|
-
say ""
|
|
339
|
-
|
|
340
|
-
# Show what bundle IDs ARE registered
|
|
341
|
-
all_bundle_ids = bundle_ids_response[:data]['bundle_ids'] || []
|
|
342
|
-
if all_bundle_ids.any?
|
|
343
|
-
say " Registered bundle IDs in your organization:", :cyan
|
|
344
|
-
all_bundle_ids.first(5).each do |bid|
|
|
345
|
-
say " • #{bid['identifier']}", :cyan
|
|
346
|
-
end
|
|
347
|
-
if all_bundle_ids.length > 5
|
|
348
|
-
say " ... and #{all_bundle_ids.length - 5} more", :cyan
|
|
349
|
-
end
|
|
350
|
-
say ""
|
|
351
|
-
end
|
|
352
|
-
|
|
353
|
-
say " Options:", :yellow
|
|
354
|
-
say " A. Register '#{bundle_id}' in App Store Connect:", :yellow
|
|
355
|
-
say " 1. Go to: https://developer.apple.com/account/resources/identifiers/add", :cyan
|
|
356
|
-
say " 2. Select 'App IDs'", :cyan
|
|
357
|
-
say " 3. Register: #{bundle_id}", :cyan
|
|
358
|
-
say " 4. Sync in web dashboard", :cyan
|
|
359
|
-
say " 5. Run 'mysigner doctor' again", :cyan
|
|
360
|
-
say ""
|
|
361
|
-
say " B. Or change your Xcode project to use an existing bundle ID", :yellow
|
|
362
|
-
say ""
|
|
363
|
-
issues << "Bundle ID #{bundle_id} not registered in App Store Connect"
|
|
364
|
-
else
|
|
365
|
-
say " ✓ Bundle ID registered", :green
|
|
366
|
-
|
|
367
|
-
# Check if profiles exist for this bundle ID
|
|
368
|
-
say " Checking provisioning profiles...", :yellow
|
|
369
|
-
|
|
370
|
-
profiles_response = client.get(
|
|
371
|
-
"/api/v1/organizations/#{config.current_organization_id}/profiles",
|
|
372
|
-
params: { bundle_id: bundle_id }
|
|
316
|
+
|
|
317
|
+
# Check 10: Project Signing Setup (if project detected and logged in)
|
|
318
|
+
if project_info && client && org_data && org_data['app_store_connect_configured']
|
|
319
|
+
say 'Checking project signing setup...', :yellow
|
|
320
|
+
|
|
321
|
+
begin
|
|
322
|
+
parser = Build::Parser.new(project_info)
|
|
323
|
+
main_target = parser.main_target
|
|
324
|
+
|
|
325
|
+
if main_target
|
|
326
|
+
target_name = main_target.name
|
|
327
|
+
bundle_id = parser.bundle_id(target_name, 'Release')
|
|
328
|
+
|
|
329
|
+
say " Project: #{target_name}", :cyan
|
|
330
|
+
say " Bundle ID: #{bundle_id}", :cyan
|
|
331
|
+
say ''
|
|
332
|
+
|
|
333
|
+
# First check if bundle ID is registered
|
|
334
|
+
say ' Checking bundle ID registration...', :yellow
|
|
335
|
+
|
|
336
|
+
bundle_ids_response = client.get(
|
|
337
|
+
"/api/v1/organizations/#{config.current_organization_id}/bundle_ids",
|
|
338
|
+
params: { q: bundle_id }
|
|
373
339
|
)
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
340
|
+
|
|
341
|
+
bundle_id_exists = (bundle_ids_response[:data]['bundle_ids'] || []).any? do |bid|
|
|
342
|
+
bid['identifier'] == bundle_id
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
if bundle_id_exists
|
|
346
|
+
say ' ✓ Bundle ID registered', :green
|
|
347
|
+
|
|
348
|
+
# Check if profiles exist for this bundle ID
|
|
349
|
+
say ' Checking provisioning profiles...', :yellow
|
|
350
|
+
|
|
351
|
+
profiles_response = client.get(
|
|
352
|
+
"/api/v1/organizations/#{config.current_organization_id}/profiles",
|
|
353
|
+
params: { bundle_id: bundle_id }
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
profiles = profiles_response[:data]['profiles'] || []
|
|
357
|
+
|
|
358
|
+
# Check for App Store profile
|
|
359
|
+
appstore_profiles = profiles.select do |p|
|
|
360
|
+
p['profile_type'] == 'IOS_APP_STORE' && p['state'] == 'ACTIVE'
|
|
389
361
|
end
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
362
|
+
|
|
363
|
+
if appstore_profiles.empty?
|
|
364
|
+
say " ✗ No App Store provisioning profile for #{bundle_id}", :red
|
|
365
|
+
say ''
|
|
366
|
+
|
|
367
|
+
if yes_with_default?('Create App Store profile automatically?', :green)
|
|
368
|
+
say ''
|
|
369
|
+
auto_create_profile(client, config, bundle_id, 'appstore')
|
|
370
|
+
else
|
|
371
|
+
issues << "Missing App Store profile for #{bundle_id} - run 'mysigner signing configure'"
|
|
372
|
+
end
|
|
373
|
+
else
|
|
374
|
+
say ' ✓ App Store provisioning profile exists', :green
|
|
375
|
+
|
|
376
|
+
# Check if expired
|
|
377
|
+
expired = appstore_profiles.select do |p|
|
|
378
|
+
expires_at = begin
|
|
379
|
+
Time.parse(p['expires_at'])
|
|
380
|
+
rescue StandardError
|
|
381
|
+
nil
|
|
382
|
+
end
|
|
383
|
+
expires_at && expires_at < Time.now
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
if expired.any?
|
|
387
|
+
say " ⚠️ #{expired.length} profile(s) expired", :yellow
|
|
388
|
+
warnings << 'Some profiles are expired - sync to refresh'
|
|
389
|
+
end
|
|
397
390
|
end
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
391
|
+
|
|
392
|
+
# Check for development profile (helpful for testing)
|
|
393
|
+
dev_profiles = profiles.select do |p|
|
|
394
|
+
p['profile_type'] == 'IOS_APP_DEVELOPMENT' && p['state'] == 'ACTIVE'
|
|
402
395
|
end
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
auto_create_profile(client, config, bundle_id, 'development')
|
|
396
|
+
|
|
397
|
+
if dev_profiles.empty?
|
|
398
|
+
say ' ⚠️ No Development profile (optional but recommended)', :yellow
|
|
399
|
+
say ''
|
|
400
|
+
say ' 📱 Development profiles let you:', :cyan
|
|
401
|
+
say ' • Test your app on physical devices (iPhone/iPad)', :cyan
|
|
402
|
+
say ' • Debug before uploading to TestFlight', :cyan
|
|
403
|
+
say ' • Share with your team for testing', :cyan
|
|
404
|
+
say ''
|
|
405
|
+
|
|
406
|
+
if yes_with_default?('Create Development profile for local testing?', :yellow)
|
|
407
|
+
say ''
|
|
408
|
+
auto_create_profile(client, config, bundle_id, 'development')
|
|
409
|
+
else
|
|
410
|
+
warnings << "No development profile - you won't be able to test on devices"
|
|
411
|
+
end
|
|
420
412
|
else
|
|
421
|
-
|
|
413
|
+
say ' ✓ Development profile exists', :green
|
|
422
414
|
end
|
|
423
415
|
else
|
|
424
|
-
say "
|
|
416
|
+
say " ✗ Bundle ID '#{bundle_id}' not registered in App Store Connect", :red
|
|
417
|
+
say ''
|
|
418
|
+
|
|
419
|
+
# Show what bundle IDs ARE registered
|
|
420
|
+
all_bundle_ids = bundle_ids_response[:data]['bundle_ids'] || []
|
|
421
|
+
if all_bundle_ids.any?
|
|
422
|
+
say ' Registered bundle IDs in your organization:', :cyan
|
|
423
|
+
all_bundle_ids.first(5).each do |bid|
|
|
424
|
+
say " • #{bid['identifier']}", :cyan
|
|
425
|
+
end
|
|
426
|
+
say " ... and #{all_bundle_ids.length - 5} more", :cyan if all_bundle_ids.length > 5
|
|
427
|
+
say ''
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
say ' Options:', :yellow
|
|
431
|
+
say " A. Register '#{bundle_id}' in App Store Connect:", :yellow
|
|
432
|
+
say ' 1. Go to: https://developer.apple.com/account/resources/identifiers/add', :cyan
|
|
433
|
+
say " 2. Select 'App IDs'", :cyan
|
|
434
|
+
say " 3. Register: #{bundle_id}", :cyan
|
|
435
|
+
say ' 4. Sync in web dashboard', :cyan
|
|
436
|
+
say " 5. Run 'mysigner doctor' again", :cyan
|
|
437
|
+
say ''
|
|
438
|
+
say ' B. Or change your Xcode project to use an existing bundle ID', :yellow
|
|
439
|
+
say ''
|
|
440
|
+
issues << "Bundle ID #{bundle_id} not registered in App Store Connect"
|
|
425
441
|
end
|
|
426
442
|
end
|
|
443
|
+
rescue StandardError => e
|
|
444
|
+
say " ⚠️ Could not check project signing: #{e.message}", :yellow
|
|
445
|
+
warnings << 'Project signing check failed'
|
|
427
446
|
end
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
447
|
+
say ''
|
|
448
|
+
elsif project_info && (!client || !org_data)
|
|
449
|
+
say '⚠️ Project detected but cannot check signing (not logged in)', :yellow
|
|
450
|
+
say ''
|
|
431
451
|
end
|
|
432
|
-
say ""
|
|
433
|
-
elsif project_info && (!client || !org_data)
|
|
434
|
-
say "⚠️ Project detected but cannot check signing (not logged in)", :yellow
|
|
435
|
-
say ""
|
|
436
452
|
end
|
|
437
|
-
end # end of check_ios block
|
|
438
453
|
|
|
439
454
|
# ==================== ANDROID CHECKS ====================
|
|
440
455
|
if check_android
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
456
|
+
say 'Checking Android development environment...', :yellow
|
|
457
|
+
android_available = false
|
|
458
|
+
|
|
459
|
+
# Check 11: Java/JDK
|
|
460
|
+
if system('which java > /dev/null 2>&1')
|
|
461
|
+
java_version = begin
|
|
462
|
+
`java -version 2>&1`.lines.first.strip
|
|
463
|
+
rescue StandardError
|
|
464
|
+
'Unknown'
|
|
465
|
+
end
|
|
466
|
+
say " ✓ Java installed: #{java_version}", :green
|
|
467
|
+
android_available = true
|
|
468
|
+
|
|
469
|
+
# Check JAVA_HOME validity
|
|
470
|
+
java_home = ENV.fetch('JAVA_HOME', nil)
|
|
471
|
+
if java_home && !java_home.empty?
|
|
472
|
+
if Dir.exist?(java_home)
|
|
473
|
+
say " ✓ JAVA_HOME: #{java_home}", :green
|
|
474
|
+
else
|
|
475
|
+
say " ✗ JAVA_HOME invalid: #{java_home}", :red
|
|
476
|
+
|
|
477
|
+
# Try to auto-detect correct JAVA_HOME
|
|
478
|
+
detected_java_home = detect_java_home
|
|
479
|
+
if detected_java_home
|
|
480
|
+
say " 💡 Detected valid Java at: #{detected_java_home}", :yellow
|
|
481
|
+
say ''
|
|
482
|
+
if yes_with_default?(' Would you like to fix JAVA_HOME in your shell config?', :green)
|
|
483
|
+
fix_java_home(detected_java_home)
|
|
484
|
+
else
|
|
485
|
+
say ' To fix manually, add to ~/.zshrc:', :yellow
|
|
486
|
+
say " export JAVA_HOME=#{detected_java_home}", :cyan
|
|
487
|
+
issues << 'JAVA_HOME points to non-existent directory'
|
|
488
|
+
end
|
|
489
|
+
else
|
|
490
|
+
issues << 'JAVA_HOME points to non-existent directory and no Java found'
|
|
491
|
+
end
|
|
492
|
+
end
|
|
455
493
|
else
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
# Try to auto-detect correct JAVA_HOME
|
|
494
|
+
# JAVA_HOME not set - try to detect and suggest
|
|
459
495
|
detected_java_home = detect_java_home
|
|
460
496
|
if detected_java_home
|
|
461
|
-
say
|
|
462
|
-
say ""
|
|
463
|
-
|
|
497
|
+
say ' ⚠️ JAVA_HOME not set', :yellow
|
|
498
|
+
say " 💡 Detected Java at: #{detected_java_home}", :yellow
|
|
499
|
+
say ''
|
|
500
|
+
if yes_with_default?(' Would you like to set JAVA_HOME in your shell config?', :green)
|
|
464
501
|
fix_java_home(detected_java_home)
|
|
465
502
|
else
|
|
466
|
-
|
|
467
|
-
say " export JAVA_HOME=#{detected_java_home}", :cyan
|
|
468
|
-
issues << "JAVA_HOME points to non-existent directory"
|
|
503
|
+
warnings << "JAVA_HOME not set (recommended: export JAVA_HOME=#{detected_java_home})"
|
|
469
504
|
end
|
|
470
505
|
else
|
|
471
|
-
|
|
506
|
+
say ' ⚠️ JAVA_HOME not set', :yellow
|
|
472
507
|
end
|
|
473
508
|
end
|
|
474
509
|
else
|
|
475
|
-
|
|
476
|
-
detected_java_home = detect_java_home
|
|
477
|
-
if detected_java_home
|
|
478
|
-
say " ⚠️ JAVA_HOME not set", :yellow
|
|
479
|
-
say " 💡 Detected Java at: #{detected_java_home}", :yellow
|
|
480
|
-
say ""
|
|
481
|
-
if yes_with_default?(" Would you like to set JAVA_HOME in your shell config?", :green)
|
|
482
|
-
fix_java_home(detected_java_home)
|
|
483
|
-
else
|
|
484
|
-
warnings << "JAVA_HOME not set (recommended: export JAVA_HOME=#{detected_java_home})"
|
|
485
|
-
end
|
|
486
|
-
else
|
|
487
|
-
say " ⚠️ JAVA_HOME not set", :yellow
|
|
488
|
-
end
|
|
510
|
+
say ' ℹ️ Java not found (required for Android)', :cyan
|
|
489
511
|
end
|
|
490
|
-
else
|
|
491
|
-
say " ℹ️ Java not found (required for Android)", :cyan
|
|
492
|
-
end
|
|
493
|
-
|
|
494
|
-
# Check 12: Android SDK
|
|
495
|
-
android_home = ENV['ANDROID_HOME'] || ENV['ANDROID_SDK_ROOT']
|
|
496
|
-
if android_home && Dir.exist?(android_home)
|
|
497
|
-
say " ✓ Android SDK: #{android_home}", :green
|
|
498
|
-
android_available = true
|
|
499
|
-
else
|
|
500
|
-
say " ℹ️ Android SDK not found (set ANDROID_HOME)", :cyan
|
|
501
|
-
end
|
|
502
512
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
513
|
+
# Check 12: Android SDK
|
|
514
|
+
android_home = ENV['ANDROID_HOME'] || ENV.fetch('ANDROID_SDK_ROOT', nil)
|
|
515
|
+
if android_home && Dir.exist?(android_home)
|
|
516
|
+
say " ✓ Android SDK: #{android_home}", :green
|
|
517
|
+
android_available = true
|
|
508
518
|
else
|
|
509
|
-
say
|
|
519
|
+
say ' ℹ️ Android SDK not found (set ANDROID_HOME)', :cyan
|
|
510
520
|
end
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
521
|
+
|
|
522
|
+
# Check 13: Gradle
|
|
523
|
+
if system('which gradle > /dev/null 2>&1') || (android_home && File.exist?("#{android_home}/../gradle"))
|
|
524
|
+
gradle_version = begin
|
|
525
|
+
`gradle --version 2>&1 | grep 'Gradle '`.strip
|
|
526
|
+
rescue StandardError
|
|
527
|
+
''
|
|
528
|
+
end
|
|
529
|
+
if gradle_version.empty?
|
|
530
|
+
say ' ✓ Gradle available (version check skipped)', :green
|
|
531
|
+
else
|
|
532
|
+
say " ✓ #{gradle_version}", :green
|
|
533
|
+
end
|
|
522
534
|
else
|
|
523
|
-
say
|
|
524
|
-
say " Configure in My Signer dashboard or run 'mysigner doctor'", :cyan
|
|
535
|
+
say ' ℹ️ Gradle not found (will use project gradlew)', :cyan
|
|
525
536
|
end
|
|
537
|
+
say ''
|
|
526
538
|
|
|
527
|
-
# Check
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
539
|
+
# Check 14: Google Play credentials (if logged in)
|
|
540
|
+
if client && org_data
|
|
541
|
+
say 'Checking Google Play configuration...', :yellow
|
|
542
|
+
|
|
543
|
+
if org_data['google_play_configured']
|
|
544
|
+
say ' ✓ Google Play credentials configured', :green
|
|
545
|
+
else
|
|
546
|
+
say ' ℹ️ Google Play not configured', :cyan
|
|
547
|
+
say " Configure in My Signer dashboard or run 'mysigner doctor'", :cyan
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
# Check for keystores
|
|
551
|
+
begin
|
|
552
|
+
require_relative '../signing/keystore_manager'
|
|
553
|
+
manager = Signing::KeystoreManager.new(client, config.current_organization_id)
|
|
554
|
+
keystores = manager.list
|
|
555
|
+
|
|
556
|
+
if keystores.any?
|
|
557
|
+
active = keystores.find { |k| k['active'] }
|
|
558
|
+
if active
|
|
559
|
+
say " ✓ Active keystore: #{active['name']}", :green
|
|
560
|
+
else
|
|
561
|
+
say " ⚠️ #{keystores.count} keystores, none active", :yellow
|
|
562
|
+
warnings << 'No active keystore - activate one with: mysigner keystore activate ID'
|
|
563
|
+
end
|
|
537
564
|
else
|
|
538
|
-
say
|
|
539
|
-
|
|
565
|
+
say ' ℹ️ No Android keystores', :cyan
|
|
566
|
+
say ' Upload with: mysigner keystore upload PATH', :cyan
|
|
540
567
|
end
|
|
541
|
-
|
|
542
|
-
say "
|
|
543
|
-
say " Upload with: mysigner keystore upload PATH", :cyan
|
|
568
|
+
rescue StandardError => e
|
|
569
|
+
say " ⚠️ Could not check keystores: #{e.message}", :yellow
|
|
544
570
|
end
|
|
545
|
-
|
|
546
|
-
say " ⚠️ Could not check keystores: #{e.message}", :yellow
|
|
571
|
+
say ''
|
|
547
572
|
end
|
|
548
|
-
say ""
|
|
549
|
-
end
|
|
550
573
|
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
574
|
+
# Check 15: Android Project Detection
|
|
575
|
+
nil
|
|
576
|
+
begin
|
|
577
|
+
android_project = Build::Detector.detect_android
|
|
578
|
+
framework = case android_project[:framework]
|
|
579
|
+
when :capacitor then 'Capacitor/Ionic'
|
|
580
|
+
when :react_native then 'React Native'
|
|
581
|
+
when :flutter then 'Flutter'
|
|
582
|
+
else 'Native Android'
|
|
583
|
+
end
|
|
584
|
+
say 'Checking Android project...', :yellow
|
|
585
|
+
say " ✓ Found #{framework} Android project", :green
|
|
586
|
+
|
|
587
|
+
# Parse project details
|
|
588
|
+
require_relative '../build/android_parser'
|
|
589
|
+
parser = Build::AndroidParser.new(android_project)
|
|
590
|
+
say " Package: #{parser.application_id}", :cyan
|
|
591
|
+
say " Version: #{parser.version_name} (#{parser.version_code})", :cyan
|
|
592
|
+
say " Gradle wrapper: #{parser.gradle_wrapper_exists? ? '✓' : '✗'}", :cyan
|
|
593
|
+
say ''
|
|
594
|
+
rescue Build::Detector::NoProjectError
|
|
595
|
+
# Not an Android project, that's fine
|
|
596
|
+
rescue StandardError => e
|
|
597
|
+
say " ⚠️ Could not analyze Android project: #{e.message}", :yellow if android_available
|
|
560
598
|
end
|
|
561
|
-
say "Checking Android project...", :yellow
|
|
562
|
-
say " ✓ Found #{framework} Android project", :green
|
|
563
|
-
|
|
564
|
-
# Parse project details
|
|
565
|
-
require_relative '../build/android_parser'
|
|
566
|
-
parser = Build::AndroidParser.new(android_project)
|
|
567
|
-
say " Package: #{parser.application_id}", :cyan
|
|
568
|
-
say " Version: #{parser.version_name} (#{parser.version_code})", :cyan
|
|
569
|
-
say " Gradle wrapper: #{parser.gradle_wrapper_exists? ? '✓' : '✗'}", :cyan
|
|
570
|
-
say ""
|
|
571
|
-
rescue Build::Detector::NoProjectError
|
|
572
|
-
# Not an Android project, that's fine
|
|
573
|
-
rescue => e
|
|
574
|
-
say " ⚠️ Could not analyze Android project: #{e.message}", :yellow if android_available
|
|
575
599
|
end
|
|
576
|
-
|
|
577
|
-
|
|
600
|
+
|
|
578
601
|
# Final Report
|
|
579
|
-
say
|
|
580
|
-
say
|
|
581
|
-
say
|
|
582
|
-
say
|
|
583
|
-
|
|
602
|
+
say '=' * 80, :cyan
|
|
603
|
+
say 'Health Report', :bold
|
|
604
|
+
say '=' * 80, :cyan
|
|
605
|
+
say ''
|
|
606
|
+
|
|
584
607
|
if issues.empty? && warnings.empty?
|
|
585
608
|
say "🎉 All checks passed! You're good to go!", :green
|
|
586
|
-
say
|
|
587
|
-
say
|
|
609
|
+
say ''
|
|
610
|
+
say 'Try: mysigner ship testflight', :cyan
|
|
588
611
|
elsif issues.empty?
|
|
589
612
|
say "⚠️ #{warnings.length} warning(s), but you're mostly good!", :yellow
|
|
590
|
-
say
|
|
613
|
+
say ''
|
|
591
614
|
warnings.each do |warning|
|
|
592
615
|
say " • #{warning}", :yellow
|
|
593
616
|
end
|
|
594
617
|
else
|
|
595
618
|
say "✗ #{issues.length} issue(s) found:", :red
|
|
596
|
-
say
|
|
619
|
+
say ''
|
|
597
620
|
issues.each do |issue|
|
|
598
621
|
say " • #{issue}", :red
|
|
599
622
|
end
|
|
600
|
-
|
|
623
|
+
|
|
601
624
|
if warnings.any?
|
|
602
|
-
say
|
|
625
|
+
say ''
|
|
603
626
|
say "⚠️ #{warnings.length} warning(s):", :yellow
|
|
604
627
|
warnings.each do |warning|
|
|
605
628
|
say " • #{warning}", :yellow
|
|
606
629
|
end
|
|
607
630
|
end
|
|
608
631
|
end
|
|
609
|
-
|
|
610
|
-
say
|
|
632
|
+
|
|
633
|
+
say ''
|
|
611
634
|
end
|
|
612
635
|
|
|
613
636
|
no_commands do
|
|
@@ -620,62 +643,66 @@ module Mysigner
|
|
|
620
643
|
# Generate a Certificate Signing Request (CSR)
|
|
621
644
|
def generate_csr(email)
|
|
622
645
|
require 'openssl'
|
|
623
|
-
|
|
624
|
-
say
|
|
625
|
-
|
|
646
|
+
|
|
647
|
+
say ' Generating CSR...', :cyan
|
|
648
|
+
|
|
626
649
|
begin
|
|
627
650
|
# Save to Downloads (visible in file picker)
|
|
628
|
-
csr_dir = File.expand_path(
|
|
651
|
+
csr_dir = File.expand_path('~/Downloads')
|
|
629
652
|
FileUtils.mkdir_p(csr_dir)
|
|
630
|
-
|
|
653
|
+
|
|
631
654
|
# Generate RSA key pair
|
|
632
655
|
key = OpenSSL::PKey::RSA.new(2048)
|
|
633
|
-
|
|
656
|
+
|
|
634
657
|
# Create CSR
|
|
635
658
|
csr = OpenSSL::X509::Request.new
|
|
636
659
|
csr.version = 0
|
|
637
660
|
csr.subject = OpenSSL::X509::Name.new([
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
661
|
+
['CN', email || 'My Signer User'],
|
|
662
|
+
['emailAddress', email || 'user@example.com']
|
|
663
|
+
])
|
|
641
664
|
csr.public_key = key.public_key
|
|
642
|
-
csr.sign(key, OpenSSL::Digest
|
|
643
|
-
|
|
665
|
+
csr.sign(key, OpenSSL::Digest.new('SHA256'))
|
|
666
|
+
|
|
644
667
|
# Generate unique filename with timestamp
|
|
645
668
|
timestamp = Time.now.strftime('%Y%m%d_%H%M%S')
|
|
646
669
|
csr_filename = "CertificateSigningRequest_#{timestamp}.certSigningRequest"
|
|
647
670
|
key_filename = "private_key_#{timestamp}.pem"
|
|
648
|
-
|
|
671
|
+
|
|
649
672
|
# Save CSR to Downloads (visible)
|
|
650
673
|
csr_path = File.join(csr_dir, csr_filename)
|
|
651
|
-
|
|
674
|
+
|
|
652
675
|
# Save private key to hidden location (secure)
|
|
653
|
-
key_dir = File.expand_path(
|
|
676
|
+
key_dir = File.expand_path('~/.mysigner/keys')
|
|
654
677
|
FileUtils.mkdir_p(key_dir)
|
|
655
678
|
key_path = File.join(key_dir, key_filename)
|
|
656
|
-
|
|
679
|
+
|
|
657
680
|
# Save CSR file
|
|
658
681
|
File.write(csr_path, csr.to_pem)
|
|
659
|
-
|
|
682
|
+
|
|
660
683
|
# Import private key directly to keychain (so certificate can pair)
|
|
661
684
|
File.write(key_path, key.to_pem)
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
import_success =
|
|
665
|
-
|
|
685
|
+
|
|
686
|
+
`security import #{key_path} -k ~/Library/Keychains/login.keychain-db -T /usr/bin/codesign -T /usr/bin/security 2>&1`
|
|
687
|
+
import_success = $CHILD_STATUS.success?
|
|
688
|
+
|
|
689
|
+
say ' ✓ CSR saved to Downloads', :green
|
|
666
690
|
if import_success
|
|
667
|
-
say
|
|
668
|
-
say " ✓ Private key imported to keychain", :green
|
|
691
|
+
say ' ✓ Private key imported to keychain', :green
|
|
669
692
|
# Clean up the file after importing
|
|
670
|
-
|
|
693
|
+
begin
|
|
694
|
+
File.delete(key_path)
|
|
695
|
+
rescue StandardError
|
|
696
|
+
nil
|
|
697
|
+
end
|
|
671
698
|
else
|
|
672
|
-
say " ✓ CSR saved to Downloads", :green
|
|
673
699
|
say " ✓ Private key saved to: #{key_path}", :green
|
|
674
|
-
say " ⚠️ Import it with: security import #{key_path} -k ~/Library/Keychains/login.keychain-db",
|
|
700
|
+
say " ⚠️ Import it with: security import #{key_path} -k ~/Library/Keychains/login.keychain-db",
|
|
701
|
+
:yellow
|
|
675
702
|
end
|
|
676
|
-
|
|
703
|
+
|
|
677
704
|
csr_path
|
|
678
|
-
rescue => e
|
|
705
|
+
rescue StandardError => e
|
|
679
706
|
say " ✗ Failed to generate CSR: #{e.message}", :red
|
|
680
707
|
nil
|
|
681
708
|
end
|
|
@@ -684,8 +711,8 @@ module Mysigner
|
|
|
684
711
|
# Helper to auto-create a provisioning profile
|
|
685
712
|
def auto_create_profile(client, config, bundle_id, profile_type)
|
|
686
713
|
say "Creating #{profile_type} profile for #{bundle_id}...", :yellow
|
|
687
|
-
say
|
|
688
|
-
|
|
714
|
+
say ''
|
|
715
|
+
|
|
689
716
|
# Map friendly names to Apple's profile types
|
|
690
717
|
apple_profile_type = case profile_type.to_s.downcase
|
|
691
718
|
when 'appstore', 'store' then 'IOS_APP_STORE'
|
|
@@ -693,40 +720,40 @@ module Mysigner
|
|
|
693
720
|
when 'adhoc' then 'IOS_APP_ADHOC'
|
|
694
721
|
else profile_type
|
|
695
722
|
end
|
|
696
|
-
|
|
723
|
+
|
|
697
724
|
begin
|
|
698
725
|
# First, ensure resources are synced
|
|
699
|
-
say
|
|
726
|
+
say ' Syncing organization resources...', :cyan
|
|
700
727
|
client.post("/api/v1/organizations/#{config.current_organization_id}/sync_app_store_connect")
|
|
701
|
-
|
|
728
|
+
|
|
702
729
|
# Wait a bit for sync to complete
|
|
703
730
|
sleep 2
|
|
704
|
-
|
|
731
|
+
|
|
705
732
|
# Check sync status
|
|
706
733
|
max_wait = 15 # seconds
|
|
707
734
|
waited = 0
|
|
708
735
|
sync_complete = false
|
|
709
|
-
|
|
736
|
+
|
|
710
737
|
while waited < max_wait
|
|
711
738
|
status_response = client.get("/api/v1/organizations/#{config.current_organization_id}/sync/status")
|
|
712
739
|
sync_data = status_response[:data]['sync']
|
|
713
|
-
|
|
714
|
-
|
|
740
|
+
|
|
741
|
+
unless sync_data['running']
|
|
715
742
|
sync_complete = true
|
|
716
743
|
break
|
|
717
744
|
end
|
|
718
|
-
|
|
745
|
+
|
|
719
746
|
sleep 1
|
|
720
747
|
waited += 1
|
|
721
748
|
end
|
|
722
|
-
|
|
749
|
+
|
|
723
750
|
if sync_complete
|
|
724
|
-
say
|
|
751
|
+
say ' ✓ Sync complete', :green
|
|
725
752
|
else
|
|
726
|
-
say
|
|
753
|
+
say ' ⚠️ Sync still running, continuing anyway...', :yellow
|
|
727
754
|
end
|
|
728
|
-
say
|
|
729
|
-
|
|
755
|
+
say ''
|
|
756
|
+
|
|
730
757
|
# Create profile
|
|
731
758
|
say " Creating #{apple_profile_type} profile...", :cyan
|
|
732
759
|
response = client.post(
|
|
@@ -736,16 +763,16 @@ module Mysigner
|
|
|
736
763
|
profile_type: apple_profile_type
|
|
737
764
|
}
|
|
738
765
|
)
|
|
739
|
-
|
|
766
|
+
|
|
740
767
|
if response[:success]
|
|
741
768
|
profile = response[:data]['profile']
|
|
742
769
|
say " ✓ Created profile: #{profile['name']}", :green
|
|
743
770
|
say " UUID: #{profile['uuid']}", :cyan
|
|
744
771
|
say " Expires: #{profile['expires_at']}", :cyan
|
|
745
|
-
say
|
|
746
|
-
|
|
772
|
+
say ''
|
|
773
|
+
|
|
747
774
|
# Download and install the profile using direct Faraday for binary data
|
|
748
|
-
say
|
|
775
|
+
say ' Downloading profile...', :cyan
|
|
749
776
|
download_url = "/api/v1/organizations/#{config.current_organization_id}/profiles/#{profile['id']}/download"
|
|
750
777
|
|
|
751
778
|
conn = Faraday.new(url: config.api_url) do |f|
|
|
@@ -760,86 +787,87 @@ module Mysigner
|
|
|
760
787
|
|
|
761
788
|
if download_response.success?
|
|
762
789
|
# Install to Xcode's provisioning profiles directory
|
|
763
|
-
profiles_dir = File.expand_path(
|
|
790
|
+
profiles_dir = File.expand_path('~/Library/MobileDevice/Provisioning Profiles')
|
|
764
791
|
FileUtils.mkdir_p(profiles_dir)
|
|
765
792
|
profile_path = File.join(profiles_dir, "#{profile['uuid']}.mobileprovision")
|
|
766
793
|
File.binwrite(profile_path, download_response.body)
|
|
767
794
|
|
|
768
|
-
say
|
|
795
|
+
say ' ✓ Profile installed to Xcode', :green
|
|
769
796
|
else
|
|
770
797
|
say " ⚠️ Could not download profile: HTTP #{download_response.status}", :yellow
|
|
771
798
|
end
|
|
772
|
-
say
|
|
799
|
+
say ''
|
|
773
800
|
true
|
|
774
801
|
else
|
|
775
|
-
say
|
|
802
|
+
say ' ✗ Failed to create profile', :red
|
|
776
803
|
false
|
|
777
804
|
end
|
|
778
805
|
rescue Mysigner::ClientError => e
|
|
779
806
|
error_msg = e.message
|
|
780
|
-
|
|
781
|
-
if error_msg.include?(
|
|
807
|
+
|
|
808
|
+
if error_msg.include?('bundle_id_not_found')
|
|
782
809
|
say " ✗ Bundle ID '#{bundle_id}' not found in App Store Connect", :red
|
|
783
|
-
say
|
|
784
|
-
say
|
|
785
|
-
say
|
|
810
|
+
say ''
|
|
811
|
+
say ' You need to register this bundle ID first:', :yellow
|
|
812
|
+
say ' 1. Go to: https://developer.apple.com/account/resources/identifiers/list', :cyan
|
|
786
813
|
say " 2. Register bundle ID: #{bundle_id}", :cyan
|
|
787
814
|
say " 3. Run 'mysigner doctor' again", :cyan
|
|
788
|
-
elsif error_msg.include?(
|
|
789
|
-
cert_type = apple_profile_type == 'IOS_APP_STORE' ?
|
|
790
|
-
cert_name = apple_profile_type == 'IOS_APP_STORE' ?
|
|
791
|
-
|
|
815
|
+
elsif error_msg.include?('certificates found') || error_msg.include?('no_certificates')
|
|
816
|
+
cert_type = apple_profile_type == 'IOS_APP_STORE' ? 'Distribution' : 'Development'
|
|
817
|
+
cert_name = apple_profile_type == 'IOS_APP_STORE' ? 'Apple Distribution' : 'Apple Development'
|
|
818
|
+
|
|
792
819
|
say " ✗ No #{cert_type} certificates found", :red
|
|
793
|
-
say
|
|
794
|
-
|
|
820
|
+
say ''
|
|
821
|
+
|
|
795
822
|
# Offer to generate CSR automatically
|
|
796
|
-
|
|
797
|
-
|
|
823
|
+
say ''
|
|
824
|
+
if yes_with_default?('Generate CSR and get step-by-step guide?', :green)
|
|
798
825
|
csr_path = generate_csr(config.user_email)
|
|
799
|
-
|
|
826
|
+
|
|
800
827
|
if csr_path
|
|
801
|
-
say
|
|
828
|
+
say ''
|
|
802
829
|
say " ✓ CSR ready: #{File.basename(csr_path)}", :green
|
|
803
|
-
say
|
|
804
|
-
say
|
|
805
|
-
say
|
|
830
|
+
say ''
|
|
831
|
+
say ' 📋 Next steps:', :cyan
|
|
832
|
+
say ' 1. Go to: https://developer.apple.com/account/resources/certificates/add',
|
|
833
|
+
:green
|
|
806
834
|
say " 2. Select: '#{cert_name}'", :green
|
|
807
835
|
say " 3. Upload CSR: #{csr_path}", :green
|
|
808
|
-
say
|
|
836
|
+
say ' 4. Download .cer file and double-click to install', :green
|
|
809
837
|
say " 5. Sync in My Signer → Run 'mysigner doctor' again", :green
|
|
810
|
-
say
|
|
838
|
+
say ''
|
|
811
839
|
end
|
|
812
840
|
else
|
|
813
|
-
say
|
|
814
|
-
say
|
|
815
|
-
say
|
|
816
|
-
|
|
841
|
+
say ' 📋 Quick guide:', :cyan
|
|
842
|
+
say ' 1. Open Keychain Access → Request Certificate (save as CSR)', :green
|
|
843
|
+
say ' 2. https://developer.apple.com/account/resources/certificates/add',
|
|
844
|
+
:green
|
|
817
845
|
say " 3. Select '#{cert_name}' → Upload CSR → Download .cer", :green
|
|
818
|
-
say
|
|
819
|
-
say
|
|
846
|
+
say ' 4. Double-click .cer to install → Sync My Signer', :green
|
|
847
|
+
say ''
|
|
820
848
|
end
|
|
821
|
-
elsif error_msg.include?(
|
|
822
|
-
say
|
|
823
|
-
say
|
|
824
|
-
say
|
|
825
|
-
say
|
|
826
|
-
say
|
|
849
|
+
elsif error_msg.include?('no_devices') || error_msg.include?('devices found')
|
|
850
|
+
say ' ✗ No test devices (needed for dev profiles)', :red
|
|
851
|
+
say ''
|
|
852
|
+
say ' 📋 Quick fix:', :cyan
|
|
853
|
+
say ' • Get UDID: Connect device → Finder → Click serial number', :green
|
|
854
|
+
say ' • Run: mysigner device add <NAME> <UDID>', :green
|
|
827
855
|
say " • Or add in: #{client.api_url}/organizations/#{config.current_organization_id}", :green
|
|
828
|
-
say
|
|
856
|
+
say ''
|
|
829
857
|
else
|
|
830
858
|
say " ✗ Failed: #{error_msg}", :red
|
|
831
859
|
end
|
|
832
|
-
say
|
|
860
|
+
say ''
|
|
833
861
|
false
|
|
834
|
-
rescue => e
|
|
862
|
+
rescue StandardError => e
|
|
835
863
|
say " ✗ Unexpected error: #{e.message}", :red
|
|
836
|
-
say
|
|
864
|
+
say ''
|
|
837
865
|
false
|
|
838
866
|
end
|
|
839
867
|
end
|
|
840
868
|
end
|
|
841
869
|
|
|
842
|
-
desc
|
|
870
|
+
desc 'sync [PLATFORM]', '🔄 Sync data from App Store Connect or Google Play'
|
|
843
871
|
long_desc <<~DESC
|
|
844
872
|
Sync your organization's data from app stores.
|
|
845
873
|
|
|
@@ -870,19 +898,19 @@ module Mysigner
|
|
|
870
898
|
sync_android(client, config)
|
|
871
899
|
when 'all', 'both'
|
|
872
900
|
sync_ios(client, config)
|
|
873
|
-
say
|
|
901
|
+
say ''
|
|
874
902
|
sync_android(client, config)
|
|
875
903
|
else
|
|
876
904
|
error "Unknown platform: #{platform}"
|
|
877
|
-
say
|
|
905
|
+
say 'Valid platforms: ios, android, all', :yellow
|
|
878
906
|
exit 1
|
|
879
907
|
end
|
|
880
908
|
end
|
|
881
909
|
|
|
882
910
|
no_commands do
|
|
883
911
|
def sync_ios(client, config)
|
|
884
|
-
say
|
|
885
|
-
say
|
|
912
|
+
say '🔄 Syncing data from App Store Connect...', :cyan
|
|
913
|
+
say ''
|
|
886
914
|
|
|
887
915
|
begin
|
|
888
916
|
response = client.post(
|
|
@@ -892,16 +920,14 @@ module Mysigner
|
|
|
892
920
|
|
|
893
921
|
if response[:success]
|
|
894
922
|
data = response[:data]['data'] || response[:data]
|
|
895
|
-
say
|
|
896
|
-
say
|
|
923
|
+
say '✓ iOS sync completed!', :green
|
|
924
|
+
say ''
|
|
897
925
|
|
|
898
|
-
if data['synced_at']
|
|
899
|
-
say "Last synced: #{data['synced_at']}", :cyan
|
|
900
|
-
end
|
|
926
|
+
say "Last synced: #{data['synced_at']}", :cyan if data['synced_at']
|
|
901
927
|
|
|
902
928
|
if data['summary']
|
|
903
|
-
say
|
|
904
|
-
say
|
|
929
|
+
say ''
|
|
930
|
+
say '📊 Summary:', :cyan
|
|
905
931
|
summary = data['summary']
|
|
906
932
|
say " • Apps: #{summary['apps']}" if summary['apps']
|
|
907
933
|
say " • Builds: #{summary['builds']}" if summary['builds']
|
|
@@ -912,14 +938,14 @@ module Mysigner
|
|
|
912
938
|
else
|
|
913
939
|
say "✗ iOS sync failed: #{response[:error]}", :red
|
|
914
940
|
end
|
|
915
|
-
rescue => e
|
|
941
|
+
rescue StandardError => e
|
|
916
942
|
say "✗ iOS sync failed: #{e.message}", :red
|
|
917
943
|
end
|
|
918
944
|
end
|
|
919
945
|
|
|
920
946
|
def sync_android(client, config)
|
|
921
|
-
say
|
|
922
|
-
say
|
|
947
|
+
say '🔄 Syncing data from Google Play...', :cyan
|
|
948
|
+
say ''
|
|
923
949
|
|
|
924
950
|
begin
|
|
925
951
|
response = client.post(
|
|
@@ -928,29 +954,29 @@ module Mysigner
|
|
|
928
954
|
)
|
|
929
955
|
|
|
930
956
|
if response[:success]
|
|
931
|
-
say
|
|
932
|
-
say
|
|
933
|
-
say
|
|
934
|
-
say
|
|
935
|
-
say
|
|
936
|
-
|
|
957
|
+
say '✓ Android sync started!', :green
|
|
958
|
+
say ''
|
|
959
|
+
say 'Sync runs in the background. Check status with:', :cyan
|
|
960
|
+
say ' mysigner apps --platform android', :green
|
|
961
|
+
say ''
|
|
962
|
+
|
|
937
963
|
# Optionally wait and show status
|
|
938
|
-
say
|
|
964
|
+
say '💡 Google Play sync may take a few minutes.', :yellow
|
|
939
965
|
say " Unlike iOS, Google Play doesn't auto-discover apps.", :yellow
|
|
940
|
-
say
|
|
966
|
+
say ' If no apps appear, add them in the web dashboard first.', :yellow
|
|
941
967
|
else
|
|
942
968
|
say "✗ Android sync failed: #{response[:error] || 'Unknown error'}", :red
|
|
943
969
|
end
|
|
944
970
|
rescue Mysigner::ClientError => e
|
|
945
|
-
if e.message.include?(
|
|
946
|
-
say
|
|
947
|
-
say
|
|
948
|
-
say
|
|
949
|
-
say
|
|
971
|
+
if e.message.include?('No active Google Play credential')
|
|
972
|
+
say '✗ Google Play not configured', :red
|
|
973
|
+
say ''
|
|
974
|
+
say 'Set up credentials first:', :yellow
|
|
975
|
+
say ' Configure Google Play in My Signer dashboard', :green
|
|
950
976
|
else
|
|
951
977
|
say "✗ Android sync failed: #{e.message}", :red
|
|
952
978
|
end
|
|
953
|
-
rescue => e
|
|
979
|
+
rescue StandardError => e
|
|
954
980
|
say "✗ Android sync failed: #{e.message}", :red
|
|
955
981
|
end
|
|
956
982
|
end
|
|
@@ -995,14 +1021,12 @@ module Mysigner
|
|
|
995
1021
|
# Fix JAVA_HOME in shell config
|
|
996
1022
|
def fix_java_home(java_home)
|
|
997
1023
|
shell_config = File.expand_path('~/.zshrc')
|
|
998
|
-
|
|
1024
|
+
|
|
999
1025
|
# Use ~/.bash_profile if zsh config doesn't exist
|
|
1000
|
-
unless File.exist?(shell_config)
|
|
1001
|
-
shell_config = File.expand_path('~/.bash_profile')
|
|
1002
|
-
end
|
|
1026
|
+
shell_config = File.expand_path('~/.bash_profile') unless File.exist?(shell_config)
|
|
1003
1027
|
|
|
1004
1028
|
# Read existing content
|
|
1005
|
-
content = File.exist?(shell_config) ? File.read(shell_config) :
|
|
1029
|
+
content = File.exist?(shell_config) ? File.read(shell_config) : ''
|
|
1006
1030
|
|
|
1007
1031
|
# Check if JAVA_HOME is already set
|
|
1008
1032
|
if content.include?('export JAVA_HOME=')
|
|
@@ -1013,18 +1037,18 @@ module Mysigner
|
|
|
1013
1037
|
else
|
|
1014
1038
|
# Append JAVA_HOME
|
|
1015
1039
|
File.open(shell_config, 'a') do |f|
|
|
1016
|
-
f.puts
|
|
1017
|
-
f.puts
|
|
1040
|
+
f.puts ''
|
|
1041
|
+
f.puts '# Added by mysigner doctor'
|
|
1018
1042
|
f.puts "export JAVA_HOME=\"#{java_home}\""
|
|
1019
1043
|
end
|
|
1020
1044
|
say " ✓ Added JAVA_HOME to #{shell_config}", :green
|
|
1021
1045
|
end
|
|
1022
1046
|
|
|
1023
|
-
say
|
|
1024
|
-
say
|
|
1047
|
+
say ''
|
|
1048
|
+
say ' To apply now, run:', :yellow
|
|
1025
1049
|
say " source #{shell_config}", :cyan
|
|
1026
|
-
say
|
|
1027
|
-
say
|
|
1050
|
+
say ''
|
|
1051
|
+
say ' Or restart your terminal.', :yellow
|
|
1028
1052
|
end
|
|
1029
1053
|
end
|
|
1030
1054
|
end
|