mysigner 0.1.2 → 0.1.4
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 +4 -0
- data/.rubocop.yml +55 -0
- data/.rubocop_todo.yml +126 -0
- data/CHANGELOG.md +96 -0
- data/Gemfile +5 -3
- data/Gemfile.lock +38 -8
- data/README.md +14 -16
- data/Rakefile +5 -3
- data/bin/console +4 -3
- data/bin/setup +3 -0
- data/certificate_.cer +0 -0
- data/exe/mysigner +19 -2
- data/iOS_App_Store_Profile.mobileprovision +1 -0
- data/iOS_Distribution_Certificate.cer +1 -0
- data/lib/mysigner/build/android_executor.rb +83 -63
- 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/cleanup/private_keys_purger.rb +41 -0
- data/lib/mysigner/cli/auth_commands.rb +771 -764
- data/lib/mysigner/cli/build_commands.rb +962 -796
- 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 +44 -1
- data/lib/mysigner/cli/diagnostic_commands.rb +667 -636
- data/lib/mysigner/cli/resource_commands.rb +1153 -985
- data/lib/mysigner/cli/validate_commands.rb +25 -25
- data/lib/mysigner/cli.rb +11 -1
- data/lib/mysigner/client.rb +27 -19
- data/lib/mysigner/config.rb +161 -60
- data/lib/mysigner/export/exporter.rb +38 -37
- data/lib/mysigner/signing/certificate_checker.rb +18 -23
- data/lib/mysigner/signing/gradle_signing_injector.rb +67 -0
- data/lib/mysigner/signing/keystore_manager.rb +81 -61
- data/lib/mysigner/signing/validator.rb +38 -40
- data/lib/mysigner/signing/wizard.rb +329 -342
- data/lib/mysigner/upload/app_store_automation.rb +96 -49
- data/lib/mysigner/upload/app_store_submission.rb +87 -92
- data/lib/mysigner/upload/asc_rest_uploader.rb +119 -0
- data/lib/mysigner/upload/play_store_uploader.rb +164 -144
- data/lib/mysigner/upload/uploader.rb +136 -115
- data/lib/mysigner/version.rb +3 -1
- data/lib/mysigner.rb +13 -11
- data/mysigner.gemspec +36 -33
- data/profile_.mobileprovision +0 -0
- data/test_manual.rb +37 -36
- metadata +44 -17
- data/.DS_Store +0 -0
|
@@ -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,599 +23,626 @@ 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
|
|
614
|
-
# Helper method for yes/no prompts with Enter defaulting to yes
|
|
637
|
+
# Helper method for yes/no prompts with Enter defaulting to yes.
|
|
638
|
+
# When stdin is not a TTY (pipe, redirect, CI), default to NO so
|
|
639
|
+
# `mysigner doctor` never silently mutates user files (e.g. ~/.zshrc)
|
|
640
|
+
# without an interactive confirmation.
|
|
615
641
|
def yes_with_default?(statement, color = nil)
|
|
642
|
+
unless $stdin.tty?
|
|
643
|
+
say "#{statement} [Y/n] (non-interactive: assuming no)", color
|
|
644
|
+
return false
|
|
645
|
+
end
|
|
616
646
|
response = ask("#{statement} [Y/n]", color).to_s.strip.downcase
|
|
617
647
|
response.empty? || response == 'y' || response == 'yes'
|
|
618
648
|
end
|
|
@@ -620,62 +650,66 @@ module Mysigner
|
|
|
620
650
|
# Generate a Certificate Signing Request (CSR)
|
|
621
651
|
def generate_csr(email)
|
|
622
652
|
require 'openssl'
|
|
623
|
-
|
|
624
|
-
say
|
|
625
|
-
|
|
653
|
+
|
|
654
|
+
say ' Generating CSR...', :cyan
|
|
655
|
+
|
|
626
656
|
begin
|
|
627
657
|
# Save to Downloads (visible in file picker)
|
|
628
|
-
csr_dir = File.expand_path(
|
|
658
|
+
csr_dir = File.expand_path('~/Downloads')
|
|
629
659
|
FileUtils.mkdir_p(csr_dir)
|
|
630
|
-
|
|
660
|
+
|
|
631
661
|
# Generate RSA key pair
|
|
632
662
|
key = OpenSSL::PKey::RSA.new(2048)
|
|
633
|
-
|
|
663
|
+
|
|
634
664
|
# Create CSR
|
|
635
665
|
csr = OpenSSL::X509::Request.new
|
|
636
666
|
csr.version = 0
|
|
637
667
|
csr.subject = OpenSSL::X509::Name.new([
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
668
|
+
['CN', email || 'My Signer User'],
|
|
669
|
+
['emailAddress', email || 'user@example.com']
|
|
670
|
+
])
|
|
641
671
|
csr.public_key = key.public_key
|
|
642
|
-
csr.sign(key, OpenSSL::Digest
|
|
643
|
-
|
|
672
|
+
csr.sign(key, OpenSSL::Digest.new('SHA256'))
|
|
673
|
+
|
|
644
674
|
# Generate unique filename with timestamp
|
|
645
675
|
timestamp = Time.now.strftime('%Y%m%d_%H%M%S')
|
|
646
676
|
csr_filename = "CertificateSigningRequest_#{timestamp}.certSigningRequest"
|
|
647
677
|
key_filename = "private_key_#{timestamp}.pem"
|
|
648
|
-
|
|
678
|
+
|
|
649
679
|
# Save CSR to Downloads (visible)
|
|
650
680
|
csr_path = File.join(csr_dir, csr_filename)
|
|
651
|
-
|
|
681
|
+
|
|
652
682
|
# Save private key to hidden location (secure)
|
|
653
|
-
key_dir = File.expand_path(
|
|
683
|
+
key_dir = File.expand_path('~/.mysigner/keys')
|
|
654
684
|
FileUtils.mkdir_p(key_dir)
|
|
655
685
|
key_path = File.join(key_dir, key_filename)
|
|
656
|
-
|
|
686
|
+
|
|
657
687
|
# Save CSR file
|
|
658
688
|
File.write(csr_path, csr.to_pem)
|
|
659
|
-
|
|
689
|
+
|
|
660
690
|
# Import private key directly to keychain (so certificate can pair)
|
|
661
691
|
File.write(key_path, key.to_pem)
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
import_success =
|
|
665
|
-
|
|
692
|
+
|
|
693
|
+
`security import #{key_path} -k ~/Library/Keychains/login.keychain-db -T /usr/bin/codesign -T /usr/bin/security 2>&1`
|
|
694
|
+
import_success = $CHILD_STATUS.success?
|
|
695
|
+
|
|
696
|
+
say ' ✓ CSR saved to Downloads', :green
|
|
666
697
|
if import_success
|
|
667
|
-
say
|
|
668
|
-
say " ✓ Private key imported to keychain", :green
|
|
698
|
+
say ' ✓ Private key imported to keychain', :green
|
|
669
699
|
# Clean up the file after importing
|
|
670
|
-
|
|
700
|
+
begin
|
|
701
|
+
File.delete(key_path)
|
|
702
|
+
rescue StandardError
|
|
703
|
+
nil
|
|
704
|
+
end
|
|
671
705
|
else
|
|
672
|
-
say " ✓ CSR saved to Downloads", :green
|
|
673
706
|
say " ✓ Private key saved to: #{key_path}", :green
|
|
674
|
-
say " ⚠️ Import it with: security import #{key_path} -k ~/Library/Keychains/login.keychain-db",
|
|
707
|
+
say " ⚠️ Import it with: security import #{key_path} -k ~/Library/Keychains/login.keychain-db",
|
|
708
|
+
:yellow
|
|
675
709
|
end
|
|
676
|
-
|
|
710
|
+
|
|
677
711
|
csr_path
|
|
678
|
-
rescue => e
|
|
712
|
+
rescue StandardError => e
|
|
679
713
|
say " ✗ Failed to generate CSR: #{e.message}", :red
|
|
680
714
|
nil
|
|
681
715
|
end
|
|
@@ -684,8 +718,8 @@ module Mysigner
|
|
|
684
718
|
# Helper to auto-create a provisioning profile
|
|
685
719
|
def auto_create_profile(client, config, bundle_id, profile_type)
|
|
686
720
|
say "Creating #{profile_type} profile for #{bundle_id}...", :yellow
|
|
687
|
-
say
|
|
688
|
-
|
|
721
|
+
say ''
|
|
722
|
+
|
|
689
723
|
# Map friendly names to Apple's profile types
|
|
690
724
|
apple_profile_type = case profile_type.to_s.downcase
|
|
691
725
|
when 'appstore', 'store' then 'IOS_APP_STORE'
|
|
@@ -693,40 +727,40 @@ module Mysigner
|
|
|
693
727
|
when 'adhoc' then 'IOS_APP_ADHOC'
|
|
694
728
|
else profile_type
|
|
695
729
|
end
|
|
696
|
-
|
|
730
|
+
|
|
697
731
|
begin
|
|
698
732
|
# First, ensure resources are synced
|
|
699
|
-
say
|
|
733
|
+
say ' Syncing organization resources...', :cyan
|
|
700
734
|
client.post("/api/v1/organizations/#{config.current_organization_id}/sync_app_store_connect")
|
|
701
|
-
|
|
735
|
+
|
|
702
736
|
# Wait a bit for sync to complete
|
|
703
737
|
sleep 2
|
|
704
|
-
|
|
738
|
+
|
|
705
739
|
# Check sync status
|
|
706
740
|
max_wait = 15 # seconds
|
|
707
741
|
waited = 0
|
|
708
742
|
sync_complete = false
|
|
709
|
-
|
|
743
|
+
|
|
710
744
|
while waited < max_wait
|
|
711
745
|
status_response = client.get("/api/v1/organizations/#{config.current_organization_id}/sync/status")
|
|
712
746
|
sync_data = status_response[:data]['sync']
|
|
713
|
-
|
|
714
|
-
|
|
747
|
+
|
|
748
|
+
unless sync_data['running']
|
|
715
749
|
sync_complete = true
|
|
716
750
|
break
|
|
717
751
|
end
|
|
718
|
-
|
|
752
|
+
|
|
719
753
|
sleep 1
|
|
720
754
|
waited += 1
|
|
721
755
|
end
|
|
722
|
-
|
|
756
|
+
|
|
723
757
|
if sync_complete
|
|
724
|
-
say
|
|
758
|
+
say ' ✓ Sync complete', :green
|
|
725
759
|
else
|
|
726
|
-
say
|
|
760
|
+
say ' ⚠️ Sync still running, continuing anyway...', :yellow
|
|
727
761
|
end
|
|
728
|
-
say
|
|
729
|
-
|
|
762
|
+
say ''
|
|
763
|
+
|
|
730
764
|
# Create profile
|
|
731
765
|
say " Creating #{apple_profile_type} profile...", :cyan
|
|
732
766
|
response = client.post(
|
|
@@ -736,16 +770,16 @@ module Mysigner
|
|
|
736
770
|
profile_type: apple_profile_type
|
|
737
771
|
}
|
|
738
772
|
)
|
|
739
|
-
|
|
773
|
+
|
|
740
774
|
if response[:success]
|
|
741
775
|
profile = response[:data]['profile']
|
|
742
776
|
say " ✓ Created profile: #{profile['name']}", :green
|
|
743
777
|
say " UUID: #{profile['uuid']}", :cyan
|
|
744
778
|
say " Expires: #{profile['expires_at']}", :cyan
|
|
745
|
-
say
|
|
746
|
-
|
|
779
|
+
say ''
|
|
780
|
+
|
|
747
781
|
# Download and install the profile using direct Faraday for binary data
|
|
748
|
-
say
|
|
782
|
+
say ' Downloading profile...', :cyan
|
|
749
783
|
download_url = "/api/v1/organizations/#{config.current_organization_id}/profiles/#{profile['id']}/download"
|
|
750
784
|
|
|
751
785
|
conn = Faraday.new(url: config.api_url) do |f|
|
|
@@ -760,86 +794,87 @@ module Mysigner
|
|
|
760
794
|
|
|
761
795
|
if download_response.success?
|
|
762
796
|
# Install to Xcode's provisioning profiles directory
|
|
763
|
-
profiles_dir = File.expand_path(
|
|
797
|
+
profiles_dir = File.expand_path('~/Library/MobileDevice/Provisioning Profiles')
|
|
764
798
|
FileUtils.mkdir_p(profiles_dir)
|
|
765
799
|
profile_path = File.join(profiles_dir, "#{profile['uuid']}.mobileprovision")
|
|
766
800
|
File.binwrite(profile_path, download_response.body)
|
|
767
801
|
|
|
768
|
-
say
|
|
802
|
+
say ' ✓ Profile installed to Xcode', :green
|
|
769
803
|
else
|
|
770
804
|
say " ⚠️ Could not download profile: HTTP #{download_response.status}", :yellow
|
|
771
805
|
end
|
|
772
|
-
say
|
|
806
|
+
say ''
|
|
773
807
|
true
|
|
774
808
|
else
|
|
775
|
-
say
|
|
809
|
+
say ' ✗ Failed to create profile', :red
|
|
776
810
|
false
|
|
777
811
|
end
|
|
778
812
|
rescue Mysigner::ClientError => e
|
|
779
813
|
error_msg = e.message
|
|
780
|
-
|
|
781
|
-
if error_msg.include?(
|
|
814
|
+
|
|
815
|
+
if error_msg.include?('bundle_id_not_found')
|
|
782
816
|
say " ✗ Bundle ID '#{bundle_id}' not found in App Store Connect", :red
|
|
783
|
-
say
|
|
784
|
-
say
|
|
785
|
-
say
|
|
817
|
+
say ''
|
|
818
|
+
say ' You need to register this bundle ID first:', :yellow
|
|
819
|
+
say ' 1. Go to: https://developer.apple.com/account/resources/identifiers/list', :cyan
|
|
786
820
|
say " 2. Register bundle ID: #{bundle_id}", :cyan
|
|
787
821
|
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
|
-
|
|
822
|
+
elsif error_msg.include?('certificates found') || error_msg.include?('no_certificates')
|
|
823
|
+
cert_type = apple_profile_type == 'IOS_APP_STORE' ? 'Distribution' : 'Development'
|
|
824
|
+
cert_name = apple_profile_type == 'IOS_APP_STORE' ? 'Apple Distribution' : 'Apple Development'
|
|
825
|
+
|
|
792
826
|
say " ✗ No #{cert_type} certificates found", :red
|
|
793
|
-
say
|
|
794
|
-
|
|
827
|
+
say ''
|
|
828
|
+
|
|
795
829
|
# Offer to generate CSR automatically
|
|
796
|
-
|
|
797
|
-
|
|
830
|
+
say ''
|
|
831
|
+
if yes_with_default?('Generate CSR and get step-by-step guide?', :green)
|
|
798
832
|
csr_path = generate_csr(config.user_email)
|
|
799
|
-
|
|
833
|
+
|
|
800
834
|
if csr_path
|
|
801
|
-
say
|
|
835
|
+
say ''
|
|
802
836
|
say " ✓ CSR ready: #{File.basename(csr_path)}", :green
|
|
803
|
-
say
|
|
804
|
-
say
|
|
805
|
-
say
|
|
837
|
+
say ''
|
|
838
|
+
say ' 📋 Next steps:', :cyan
|
|
839
|
+
say ' 1. Go to: https://developer.apple.com/account/resources/certificates/add',
|
|
840
|
+
:green
|
|
806
841
|
say " 2. Select: '#{cert_name}'", :green
|
|
807
842
|
say " 3. Upload CSR: #{csr_path}", :green
|
|
808
|
-
say
|
|
843
|
+
say ' 4. Download .cer file and double-click to install', :green
|
|
809
844
|
say " 5. Sync in My Signer → Run 'mysigner doctor' again", :green
|
|
810
|
-
say
|
|
845
|
+
say ''
|
|
811
846
|
end
|
|
812
847
|
else
|
|
813
|
-
say
|
|
814
|
-
say
|
|
815
|
-
say
|
|
816
|
-
|
|
848
|
+
say ' 📋 Quick guide:', :cyan
|
|
849
|
+
say ' 1. Open Keychain Access → Request Certificate (save as CSR)', :green
|
|
850
|
+
say ' 2. https://developer.apple.com/account/resources/certificates/add',
|
|
851
|
+
:green
|
|
817
852
|
say " 3. Select '#{cert_name}' → Upload CSR → Download .cer", :green
|
|
818
|
-
say
|
|
819
|
-
say
|
|
853
|
+
say ' 4. Double-click .cer to install → Sync My Signer', :green
|
|
854
|
+
say ''
|
|
820
855
|
end
|
|
821
|
-
elsif error_msg.include?(
|
|
822
|
-
say
|
|
823
|
-
say
|
|
824
|
-
say
|
|
825
|
-
say
|
|
826
|
-
say
|
|
856
|
+
elsif error_msg.include?('no_devices') || error_msg.include?('devices found')
|
|
857
|
+
say ' ✗ No test devices (needed for dev profiles)', :red
|
|
858
|
+
say ''
|
|
859
|
+
say ' 📋 Quick fix:', :cyan
|
|
860
|
+
say ' • Get UDID: Connect device → Finder → Click serial number', :green
|
|
861
|
+
say ' • Run: mysigner device add <NAME> <UDID>', :green
|
|
827
862
|
say " • Or add in: #{client.api_url}/organizations/#{config.current_organization_id}", :green
|
|
828
|
-
say
|
|
863
|
+
say ''
|
|
829
864
|
else
|
|
830
865
|
say " ✗ Failed: #{error_msg}", :red
|
|
831
866
|
end
|
|
832
|
-
say
|
|
867
|
+
say ''
|
|
833
868
|
false
|
|
834
|
-
rescue => e
|
|
869
|
+
rescue StandardError => e
|
|
835
870
|
say " ✗ Unexpected error: #{e.message}", :red
|
|
836
|
-
say
|
|
871
|
+
say ''
|
|
837
872
|
false
|
|
838
873
|
end
|
|
839
874
|
end
|
|
840
875
|
end
|
|
841
876
|
|
|
842
|
-
desc
|
|
877
|
+
desc 'sync [PLATFORM]', '🔄 Sync data from App Store Connect or Google Play'
|
|
843
878
|
long_desc <<~DESC
|
|
844
879
|
Sync your organization's data from app stores.
|
|
845
880
|
|
|
@@ -870,19 +905,19 @@ module Mysigner
|
|
|
870
905
|
sync_android(client, config)
|
|
871
906
|
when 'all', 'both'
|
|
872
907
|
sync_ios(client, config)
|
|
873
|
-
say
|
|
908
|
+
say ''
|
|
874
909
|
sync_android(client, config)
|
|
875
910
|
else
|
|
876
911
|
error "Unknown platform: #{platform}"
|
|
877
|
-
say
|
|
912
|
+
say 'Valid platforms: ios, android, all', :yellow
|
|
878
913
|
exit 1
|
|
879
914
|
end
|
|
880
915
|
end
|
|
881
916
|
|
|
882
917
|
no_commands do
|
|
883
918
|
def sync_ios(client, config)
|
|
884
|
-
say
|
|
885
|
-
say
|
|
919
|
+
say '🔄 Syncing data from App Store Connect...', :cyan
|
|
920
|
+
say ''
|
|
886
921
|
|
|
887
922
|
begin
|
|
888
923
|
response = client.post(
|
|
@@ -892,16 +927,14 @@ module Mysigner
|
|
|
892
927
|
|
|
893
928
|
if response[:success]
|
|
894
929
|
data = response[:data]['data'] || response[:data]
|
|
895
|
-
say
|
|
896
|
-
say
|
|
930
|
+
say '✓ iOS sync completed!', :green
|
|
931
|
+
say ''
|
|
897
932
|
|
|
898
|
-
if data['synced_at']
|
|
899
|
-
say "Last synced: #{data['synced_at']}", :cyan
|
|
900
|
-
end
|
|
933
|
+
say "Last synced: #{data['synced_at']}", :cyan if data['synced_at']
|
|
901
934
|
|
|
902
935
|
if data['summary']
|
|
903
|
-
say
|
|
904
|
-
say
|
|
936
|
+
say ''
|
|
937
|
+
say '📊 Summary:', :cyan
|
|
905
938
|
summary = data['summary']
|
|
906
939
|
say " • Apps: #{summary['apps']}" if summary['apps']
|
|
907
940
|
say " • Builds: #{summary['builds']}" if summary['builds']
|
|
@@ -912,14 +945,14 @@ module Mysigner
|
|
|
912
945
|
else
|
|
913
946
|
say "✗ iOS sync failed: #{response[:error]}", :red
|
|
914
947
|
end
|
|
915
|
-
rescue => e
|
|
948
|
+
rescue StandardError => e
|
|
916
949
|
say "✗ iOS sync failed: #{e.message}", :red
|
|
917
950
|
end
|
|
918
951
|
end
|
|
919
952
|
|
|
920
953
|
def sync_android(client, config)
|
|
921
|
-
say
|
|
922
|
-
say
|
|
954
|
+
say '🔄 Syncing data from Google Play...', :cyan
|
|
955
|
+
say ''
|
|
923
956
|
|
|
924
957
|
begin
|
|
925
958
|
response = client.post(
|
|
@@ -928,29 +961,29 @@ module Mysigner
|
|
|
928
961
|
)
|
|
929
962
|
|
|
930
963
|
if response[:success]
|
|
931
|
-
say
|
|
932
|
-
say
|
|
933
|
-
say
|
|
934
|
-
say
|
|
935
|
-
say
|
|
936
|
-
|
|
964
|
+
say '✓ Android sync started!', :green
|
|
965
|
+
say ''
|
|
966
|
+
say 'Sync runs in the background. Check status with:', :cyan
|
|
967
|
+
say ' mysigner apps --platform android', :green
|
|
968
|
+
say ''
|
|
969
|
+
|
|
937
970
|
# Optionally wait and show status
|
|
938
|
-
say
|
|
971
|
+
say '💡 Google Play sync may take a few minutes.', :yellow
|
|
939
972
|
say " Unlike iOS, Google Play doesn't auto-discover apps.", :yellow
|
|
940
|
-
say
|
|
973
|
+
say ' If no apps appear, add them in the web dashboard first.', :yellow
|
|
941
974
|
else
|
|
942
975
|
say "✗ Android sync failed: #{response[:error] || 'Unknown error'}", :red
|
|
943
976
|
end
|
|
944
977
|
rescue Mysigner::ClientError => e
|
|
945
|
-
if e.message.include?(
|
|
946
|
-
say
|
|
947
|
-
say
|
|
948
|
-
say
|
|
949
|
-
say
|
|
978
|
+
if e.message.include?('No active Google Play credential')
|
|
979
|
+
say '✗ Google Play not configured', :red
|
|
980
|
+
say ''
|
|
981
|
+
say 'Set up credentials first:', :yellow
|
|
982
|
+
say ' Configure Google Play in My Signer dashboard', :green
|
|
950
983
|
else
|
|
951
984
|
say "✗ Android sync failed: #{e.message}", :red
|
|
952
985
|
end
|
|
953
|
-
rescue => e
|
|
986
|
+
rescue StandardError => e
|
|
954
987
|
say "✗ Android sync failed: #{e.message}", :red
|
|
955
988
|
end
|
|
956
989
|
end
|
|
@@ -995,14 +1028,12 @@ module Mysigner
|
|
|
995
1028
|
# Fix JAVA_HOME in shell config
|
|
996
1029
|
def fix_java_home(java_home)
|
|
997
1030
|
shell_config = File.expand_path('~/.zshrc')
|
|
998
|
-
|
|
1031
|
+
|
|
999
1032
|
# 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
|
|
1033
|
+
shell_config = File.expand_path('~/.bash_profile') unless File.exist?(shell_config)
|
|
1003
1034
|
|
|
1004
1035
|
# Read existing content
|
|
1005
|
-
content = File.exist?(shell_config) ? File.read(shell_config) :
|
|
1036
|
+
content = File.exist?(shell_config) ? File.read(shell_config) : ''
|
|
1006
1037
|
|
|
1007
1038
|
# Check if JAVA_HOME is already set
|
|
1008
1039
|
if content.include?('export JAVA_HOME=')
|
|
@@ -1013,18 +1044,18 @@ module Mysigner
|
|
|
1013
1044
|
else
|
|
1014
1045
|
# Append JAVA_HOME
|
|
1015
1046
|
File.open(shell_config, 'a') do |f|
|
|
1016
|
-
f.puts
|
|
1017
|
-
f.puts
|
|
1047
|
+
f.puts ''
|
|
1048
|
+
f.puts '# Added by mysigner doctor'
|
|
1018
1049
|
f.puts "export JAVA_HOME=\"#{java_home}\""
|
|
1019
1050
|
end
|
|
1020
1051
|
say " ✓ Added JAVA_HOME to #{shell_config}", :green
|
|
1021
1052
|
end
|
|
1022
1053
|
|
|
1023
|
-
say
|
|
1024
|
-
say
|
|
1054
|
+
say ''
|
|
1055
|
+
say ' To apply now, run:', :yellow
|
|
1025
1056
|
say " source #{shell_config}", :cyan
|
|
1026
|
-
say
|
|
1027
|
-
say
|
|
1057
|
+
say ''
|
|
1058
|
+
say ' Or restart your terminal.', :yellow
|
|
1028
1059
|
end
|
|
1029
1060
|
end
|
|
1030
1061
|
end
|