mysigner 0.1.2 → 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/.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 +13 -15
- 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 +880 -902
- data/lib/mysigner/cli/validate_commands.rb +25 -25
- data/lib/mysigner/cli.rb +3 -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 +37 -16
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Mysigner
|
|
2
4
|
class CLI < Thor
|
|
3
5
|
module ResourceCommands
|
|
4
6
|
def self.included(base)
|
|
5
7
|
base.class_eval do
|
|
6
|
-
desc
|
|
8
|
+
desc 'devices', 'List registered test devices (UDIDs)'
|
|
7
9
|
method_option :platform, type: :string, aliases: '-p', desc: 'Filter by platform (IOS, MAC_OS, TV_OS)'
|
|
8
10
|
method_option :status, type: :string, aliases: '-s', desc: 'Filter by status (ENABLED, DISABLED)'
|
|
9
11
|
method_option :search, type: :string, aliases: '-q', desc: 'Search by name or UDID'
|
|
@@ -13,8 +15,8 @@ module Mysigner
|
|
|
13
15
|
config = load_config
|
|
14
16
|
client = create_client(config)
|
|
15
17
|
|
|
16
|
-
say
|
|
17
|
-
say
|
|
18
|
+
say '📱 Devices', :cyan
|
|
19
|
+
say ''
|
|
18
20
|
|
|
19
21
|
# Build query params
|
|
20
22
|
params = {
|
|
@@ -31,8 +33,8 @@ module Mysigner
|
|
|
31
33
|
pagination = response[:data]['pagination']
|
|
32
34
|
|
|
33
35
|
if devices.empty?
|
|
34
|
-
say
|
|
35
|
-
say
|
|
36
|
+
say 'No devices found', :yellow
|
|
37
|
+
say ''
|
|
36
38
|
say "Tip: Register a device with 'mysigner device add NAME UDID'", :yellow
|
|
37
39
|
return
|
|
38
40
|
end
|
|
@@ -41,21 +43,19 @@ module Mysigner
|
|
|
41
43
|
devices.each do |device|
|
|
42
44
|
status_icon = device['status'] == 'ENABLED' ? '✓' : '✗'
|
|
43
45
|
status_color = device['status'] == 'ENABLED' ? :green : :red
|
|
44
|
-
|
|
46
|
+
|
|
45
47
|
say " #{status_icon} #{device['name']} (ID: #{device['id']})", status_color
|
|
46
48
|
say " UDID: #{device['udid']}"
|
|
47
49
|
say " Platform: #{device['platform']} | Class: #{device['device_class']}"
|
|
48
50
|
say " Status: #{device['status']}"
|
|
49
|
-
say
|
|
51
|
+
say ''
|
|
50
52
|
end
|
|
51
53
|
|
|
52
54
|
# Show pagination
|
|
53
55
|
if pagination
|
|
54
56
|
say "Page #{pagination['page']} of #{pagination['total_pages']} (#{pagination['total']} total)", :yellow
|
|
55
57
|
|
|
56
|
-
if pagination['page'] < pagination['total_pages']
|
|
57
|
-
say "Run with --page #{pagination['page'] + 1} to see more", :yellow
|
|
58
|
-
end
|
|
58
|
+
say "Run with --page #{pagination['page'] + 1} to see more", :yellow if pagination['page'] < pagination['total_pages']
|
|
59
59
|
end
|
|
60
60
|
rescue Mysigner::ClientError => e
|
|
61
61
|
error "Failed to fetch devices: #{e.message}"
|
|
@@ -63,57 +63,57 @@ module Mysigner
|
|
|
63
63
|
end
|
|
64
64
|
end
|
|
65
65
|
|
|
66
|
-
desc
|
|
66
|
+
desc 'device SUBCOMMAND', 'Manage test devices (detect, add, update)'
|
|
67
67
|
long_desc <<~DESC
|
|
68
68
|
Register and manage test devices for development builds.
|
|
69
|
-
|
|
69
|
+
|
|
70
70
|
WHY REGISTER DEVICES?
|
|
71
|
-
|
|
71
|
+
|
|
72
72
|
To install development/adhoc builds on physical devices, you must register
|
|
73
73
|
their UDIDs (Unique Device Identifiers) with Apple and include them in your
|
|
74
74
|
provisioning profiles.
|
|
75
|
-
|
|
75
|
+
|
|
76
76
|
SUBCOMMANDS:
|
|
77
|
-
|
|
77
|
+
|
|
78
78
|
mysigner device detect
|
|
79
79
|
Auto-detect connected iOS devices and show their UDIDs
|
|
80
|
-
|
|
80
|
+
#{' '}
|
|
81
81
|
mysigner device add NAME UDID [--platform IOS]
|
|
82
82
|
Register a new device for testing
|
|
83
|
-
|
|
83
|
+
#{' '}
|
|
84
84
|
mysigner device update ID NEW_NAME
|
|
85
85
|
Rename an existing device
|
|
86
|
-
|
|
86
|
+
|
|
87
87
|
HOW TO GET A DEVICE UDID:
|
|
88
|
-
|
|
88
|
+
|
|
89
89
|
Method 1 - Auto-detect (Recommended):
|
|
90
90
|
mysigner device detect
|
|
91
|
-
|
|
91
|
+
#{' '}
|
|
92
92
|
This will find all connected iOS devices and let you register them
|
|
93
93
|
interactively. No need to open any other apps.
|
|
94
|
-
|
|
94
|
+
|
|
95
95
|
Method 2 - Via Finder:
|
|
96
96
|
1. Connect your iPhone/iPad to your Mac
|
|
97
97
|
2. Open Finder and select your device in the sidebar
|
|
98
98
|
3. Click on the device info to reveal UDID
|
|
99
99
|
4. Right-click → Copy UDID
|
|
100
|
-
|
|
100
|
+
|
|
101
101
|
EXAMPLES:
|
|
102
|
-
|
|
102
|
+
|
|
103
103
|
# Register your iPhone
|
|
104
104
|
mysigner device add "My iPhone 15" 00008030-001A1B2C3D4E567F
|
|
105
|
-
|
|
105
|
+
#{' '}
|
|
106
106
|
# Register an iPad
|
|
107
107
|
mysigner device add "Test iPad" da83bb40dba39e35d258988d856508798db7afba
|
|
108
|
-
|
|
108
|
+
#{' '}
|
|
109
109
|
# Register a Mac for Mac Catalyst apps
|
|
110
110
|
mysigner device add "MacBook Pro" ABC123... --platform MAC_OS
|
|
111
|
-
|
|
111
|
+
#{' '}
|
|
112
112
|
# Rename a device (use ID from 'mysigner devices' list)
|
|
113
113
|
mysigner device update 42 "John's iPhone"
|
|
114
|
-
|
|
114
|
+
|
|
115
115
|
NOTES:
|
|
116
|
-
|
|
116
|
+
|
|
117
117
|
• UDIDs are 40 hex characters (0-9, a-f) or 25 characters for newer devices
|
|
118
118
|
• You can register up to 100 devices per year per account
|
|
119
119
|
• After registering, regenerate your provisioning profiles to include the device
|
|
@@ -129,14 +129,14 @@ module Mysigner
|
|
|
129
129
|
detect_connected_devices(config, client)
|
|
130
130
|
when 'add'
|
|
131
131
|
if args.length < 2
|
|
132
|
-
error
|
|
133
|
-
say
|
|
134
|
-
say
|
|
135
|
-
say
|
|
132
|
+
error 'Usage: mysigner device add NAME UDID [--platform IOS]'
|
|
133
|
+
say ''
|
|
134
|
+
say 'Example: mysigner device add "My iPhone" 00008030-001A1B2C3D4E567F', :yellow
|
|
135
|
+
say ''
|
|
136
136
|
say "💡 Don't know your UDID? Run:", :cyan
|
|
137
|
-
say
|
|
138
|
-
say
|
|
139
|
-
say
|
|
137
|
+
say ' mysigner device detect', :cyan
|
|
138
|
+
say ''
|
|
139
|
+
say ' This will auto-detect connected devices and let you register them.', :cyan
|
|
140
140
|
exit 1
|
|
141
141
|
end
|
|
142
142
|
|
|
@@ -144,8 +144,8 @@ module Mysigner
|
|
|
144
144
|
udid = args[1]
|
|
145
145
|
platform = options[:platform].upcase
|
|
146
146
|
|
|
147
|
-
say
|
|
148
|
-
say
|
|
147
|
+
say '📱 Registering device...', :cyan
|
|
148
|
+
say ''
|
|
149
149
|
|
|
150
150
|
begin
|
|
151
151
|
response = client.post(
|
|
@@ -158,15 +158,15 @@ module Mysigner
|
|
|
158
158
|
)
|
|
159
159
|
|
|
160
160
|
device = response[:data]['device']
|
|
161
|
-
say
|
|
162
|
-
say
|
|
163
|
-
say
|
|
161
|
+
say '✓ Device registered successfully!', :green
|
|
162
|
+
say ''
|
|
163
|
+
say 'Details:', :bold
|
|
164
164
|
say " Name: #{device['name']}"
|
|
165
165
|
say " UDID: #{device['udid']}"
|
|
166
166
|
say " Platform: #{device['platform']}"
|
|
167
167
|
say " Status: #{device['status']}"
|
|
168
168
|
rescue Mysigner::ValidationError => e
|
|
169
|
-
error
|
|
169
|
+
error 'Validation failed:'
|
|
170
170
|
if e.details
|
|
171
171
|
e.details.each do |field, errors|
|
|
172
172
|
say " #{field}: #{errors.join(', ')}", :red
|
|
@@ -177,8 +177,8 @@ module Mysigner
|
|
|
177
177
|
say " Suggestion: #{e.suggestion}", :yellow if e.suggestion
|
|
178
178
|
exit 1
|
|
179
179
|
rescue Mysigner::ClientError => e
|
|
180
|
-
if e.message.include?(
|
|
181
|
-
error
|
|
180
|
+
if e.message.include?('already exists')
|
|
181
|
+
error 'Device with this UDID already exists'
|
|
182
182
|
else
|
|
183
183
|
error "Failed to register device: #{e.message}"
|
|
184
184
|
end
|
|
@@ -186,11 +186,11 @@ module Mysigner
|
|
|
186
186
|
end
|
|
187
187
|
when 'update'
|
|
188
188
|
if args.length < 2
|
|
189
|
-
error
|
|
190
|
-
say
|
|
189
|
+
error 'Usage: mysigner device update ID NEW_NAME'
|
|
190
|
+
say ''
|
|
191
191
|
say "Example: mysigner device update 42 \"John's iPhone\"", :yellow
|
|
192
|
-
say
|
|
193
|
-
say
|
|
192
|
+
say ''
|
|
193
|
+
say '💡 To get device IDs:', :cyan
|
|
194
194
|
say " Run 'mysigner devices' to see all devices with their IDs", :cyan
|
|
195
195
|
exit 1
|
|
196
196
|
end
|
|
@@ -198,8 +198,8 @@ module Mysigner
|
|
|
198
198
|
device_id = args[0]
|
|
199
199
|
new_name = args[1]
|
|
200
200
|
|
|
201
|
-
say
|
|
202
|
-
say
|
|
201
|
+
say '📱 Updating device...', :cyan
|
|
202
|
+
say ''
|
|
203
203
|
|
|
204
204
|
begin
|
|
205
205
|
# Get device details first
|
|
@@ -208,7 +208,7 @@ module Mysigner
|
|
|
208
208
|
|
|
209
209
|
say "Current name: #{device['name']}", :yellow
|
|
210
210
|
say "New name: #{new_name}", :green
|
|
211
|
-
say
|
|
211
|
+
say ''
|
|
212
212
|
|
|
213
213
|
# Update device
|
|
214
214
|
response = client.patch(
|
|
@@ -217,9 +217,9 @@ module Mysigner
|
|
|
217
217
|
)
|
|
218
218
|
|
|
219
219
|
updated_device = response[:data]['device']
|
|
220
|
-
say
|
|
221
|
-
say
|
|
222
|
-
say
|
|
220
|
+
say '✓ Device updated successfully!', :green
|
|
221
|
+
say ''
|
|
222
|
+
say 'Details:', :bold
|
|
223
223
|
say " Name: #{updated_device['name']}"
|
|
224
224
|
say " UDID: #{updated_device['udid']}"
|
|
225
225
|
say " Platform: #{updated_device['platform']}"
|
|
@@ -234,7 +234,7 @@ module Mysigner
|
|
|
234
234
|
invoke :help, ['device']
|
|
235
235
|
else
|
|
236
236
|
error "Unknown action: #{action}"
|
|
237
|
-
say
|
|
237
|
+
say 'Available actions: detect, add, update, help', :yellow
|
|
238
238
|
exit 1
|
|
239
239
|
end
|
|
240
240
|
end
|
|
@@ -242,34 +242,32 @@ module Mysigner
|
|
|
242
242
|
private
|
|
243
243
|
|
|
244
244
|
def detect_connected_devices(config, client)
|
|
245
|
-
say
|
|
246
|
-
say
|
|
245
|
+
say '🔍 Detecting connected iOS devices...', :cyan
|
|
246
|
+
say ''
|
|
247
247
|
|
|
248
248
|
devices = []
|
|
249
249
|
|
|
250
250
|
# Try system_profiler first (built-in macOS)
|
|
251
|
-
if system(
|
|
251
|
+
if system('which system_profiler > /dev/null 2>&1')
|
|
252
252
|
output = `system_profiler SPUSBDataType 2>/dev/null`
|
|
253
|
-
|
|
253
|
+
|
|
254
254
|
# Parse iOS devices from system_profiler output
|
|
255
255
|
current_device = nil
|
|
256
256
|
output.each_line do |line|
|
|
257
257
|
if line =~ /^\s{4}(\S.+):$/
|
|
258
258
|
# New top-level USB device
|
|
259
|
-
current_device = { name:
|
|
259
|
+
current_device = { name: ::Regexp.last_match(1).strip }
|
|
260
260
|
elsif current_device
|
|
261
261
|
if line =~ /Serial Number:\s*([A-Fa-f0-9-]+)/
|
|
262
|
-
serial =
|
|
262
|
+
serial = ::Regexp.last_match(1).gsub('-', '')
|
|
263
263
|
# iOS device serials are typically 24-40 hex chars
|
|
264
|
-
|
|
265
|
-
current_device[:udid] = serial
|
|
266
|
-
end
|
|
264
|
+
current_device[:udid] = serial if serial.length.between?(24, 40)
|
|
267
265
|
elsif line =~ /Product ID:\s*(0x12[aA][0-9a-fA-F])/
|
|
268
266
|
# Apple mobile device product IDs start with 0x12a
|
|
269
267
|
current_device[:is_ios] = true
|
|
270
268
|
end
|
|
271
269
|
end
|
|
272
|
-
|
|
270
|
+
|
|
273
271
|
if current_device && current_device[:udid] && current_device[:is_ios]
|
|
274
272
|
devices << current_device
|
|
275
273
|
current_device = nil
|
|
@@ -278,76 +276,74 @@ module Mysigner
|
|
|
278
276
|
end
|
|
279
277
|
|
|
280
278
|
# Also try idevice_id if available (more reliable)
|
|
281
|
-
if system(
|
|
279
|
+
if system('which idevice_id > /dev/null 2>&1')
|
|
282
280
|
output = `idevice_id -l 2>/dev/null`.strip
|
|
283
281
|
output.each_line do |line|
|
|
284
282
|
udid = line.strip
|
|
285
283
|
next if udid.empty?
|
|
286
|
-
|
|
284
|
+
|
|
287
285
|
# Get device name if ideviceinfo is available
|
|
288
|
-
name =
|
|
289
|
-
if system(
|
|
286
|
+
name = 'iOS Device'
|
|
287
|
+
if system('which ideviceinfo > /dev/null 2>&1')
|
|
290
288
|
device_name = `ideviceinfo -u #{udid} -k DeviceName 2>/dev/null`.strip
|
|
291
289
|
name = device_name unless device_name.empty?
|
|
292
290
|
end
|
|
293
|
-
|
|
291
|
+
|
|
294
292
|
# Avoid duplicates
|
|
295
|
-
unless devices.any? { |d| d[:udid] == udid }
|
|
296
|
-
devices << { name: name, udid: udid }
|
|
297
|
-
end
|
|
293
|
+
devices << { name: name, udid: udid } unless devices.any? { |d| d[:udid] == udid }
|
|
298
294
|
end
|
|
299
295
|
end
|
|
300
296
|
|
|
301
297
|
# Also try xcrun xctrace (Xcode command line tools)
|
|
302
|
-
if devices.empty? && system(
|
|
298
|
+
if devices.empty? && system('which xcrun > /dev/null 2>&1')
|
|
303
299
|
output = `xcrun xctrace list devices 2>/dev/null`
|
|
304
300
|
in_devices_section = false
|
|
305
|
-
|
|
301
|
+
|
|
306
302
|
output.each_line do |line|
|
|
307
303
|
line = line.strip
|
|
308
|
-
|
|
304
|
+
|
|
309
305
|
# Track sections
|
|
310
|
-
if line ==
|
|
306
|
+
if line == '== Devices =='
|
|
311
307
|
in_devices_section = true
|
|
312
308
|
next
|
|
313
|
-
elsif line ==
|
|
309
|
+
elsif line == '== Simulators =='
|
|
314
310
|
in_devices_section = false
|
|
315
311
|
next
|
|
316
312
|
end
|
|
317
|
-
|
|
313
|
+
|
|
318
314
|
next unless in_devices_section
|
|
319
315
|
next if line.empty?
|
|
320
|
-
|
|
316
|
+
|
|
321
317
|
# Format: "Name (Version) (UDID)" - iOS devices have UDIDs starting with 0000
|
|
322
318
|
# Skip Macs which have UUID format
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
319
|
+
next unless line =~ /^(.+?)\s+\([^)]+\)\s+\((0000[A-Fa-f0-9-]+)\)\s*$/
|
|
320
|
+
|
|
321
|
+
name = ::Regexp.last_match(1).strip
|
|
322
|
+
udid = ::Regexp.last_match(2).gsub('-', '')
|
|
323
|
+
devices << { name: name, udid: udid }
|
|
328
324
|
end
|
|
329
325
|
end
|
|
330
326
|
|
|
331
327
|
if devices.empty?
|
|
332
|
-
say
|
|
333
|
-
say
|
|
334
|
-
say
|
|
335
|
-
say
|
|
336
|
-
say
|
|
328
|
+
say 'No iOS devices detected.', :yellow
|
|
329
|
+
say ''
|
|
330
|
+
say 'Make sure:', :cyan
|
|
331
|
+
say ' 1. Your device is connected via USB', :cyan
|
|
332
|
+
say ' 2. The device is unlocked', :cyan
|
|
337
333
|
say " 3. You've trusted this computer on the device", :cyan
|
|
338
|
-
say
|
|
339
|
-
say
|
|
340
|
-
say
|
|
334
|
+
say ''
|
|
335
|
+
say '💡 For better detection, install libimobiledevice:', :yellow
|
|
336
|
+
say ' brew install libimobiledevice', :yellow
|
|
341
337
|
return
|
|
342
338
|
end
|
|
343
339
|
|
|
344
340
|
say "Found #{devices.length} device(s):", :green
|
|
345
|
-
say
|
|
341
|
+
say ''
|
|
346
342
|
|
|
347
343
|
devices.each_with_index do |device, idx|
|
|
348
344
|
say " #{idx + 1}. #{device[:name]}", :green
|
|
349
345
|
say " UDID: #{device[:udid]}", :white
|
|
350
|
-
say
|
|
346
|
+
say ''
|
|
351
347
|
end
|
|
352
348
|
|
|
353
349
|
# Check if running interactively
|
|
@@ -355,20 +351,20 @@ module Mysigner
|
|
|
355
351
|
|
|
356
352
|
# Ask if user wants to register
|
|
357
353
|
say "Would you like to register a device? (Enter number, or 'n' to skip)", :cyan
|
|
358
|
-
choice = ask(
|
|
354
|
+
choice = ask('>')&.strip || ''
|
|
359
355
|
|
|
360
356
|
return if choice.downcase == 'n' || choice.empty?
|
|
361
357
|
|
|
362
358
|
idx = choice.to_i - 1
|
|
363
359
|
if idx >= 0 && idx < devices.length
|
|
364
360
|
device = devices[idx]
|
|
365
|
-
|
|
366
|
-
say
|
|
361
|
+
|
|
362
|
+
say ''
|
|
367
363
|
say "Enter a name for this device (or press Enter to use '#{device[:name]}'):", :cyan
|
|
368
|
-
custom_name = ask(
|
|
364
|
+
custom_name = ask('>')&.strip || ''
|
|
369
365
|
name = custom_name.empty? ? device[:name] : custom_name
|
|
370
366
|
|
|
371
|
-
say
|
|
367
|
+
say ''
|
|
372
368
|
say "📱 Registering '#{name}'...", :cyan
|
|
373
369
|
|
|
374
370
|
begin
|
|
@@ -382,58 +378,59 @@ module Mysigner
|
|
|
382
378
|
)
|
|
383
379
|
|
|
384
380
|
registered = response[:data]['device']
|
|
385
|
-
say
|
|
386
|
-
say
|
|
381
|
+
say ''
|
|
382
|
+
say '✓ Device registered successfully!', :green
|
|
387
383
|
say " Name: #{registered['name']}"
|
|
388
384
|
say " UDID: #{registered['udid']}"
|
|
389
385
|
say " Platform: #{registered['platform']}"
|
|
390
386
|
rescue Mysigner::ClientError => e
|
|
391
|
-
if e.message.include?(
|
|
392
|
-
say
|
|
393
|
-
say
|
|
387
|
+
if e.message.include?('already exists')
|
|
388
|
+
say ''
|
|
389
|
+
say 'ℹ️ Device already registered', :yellow
|
|
394
390
|
else
|
|
395
391
|
error "Failed to register: #{e.message}"
|
|
396
392
|
end
|
|
397
393
|
end
|
|
398
394
|
else
|
|
399
|
-
say
|
|
395
|
+
say 'Invalid selection', :red
|
|
400
396
|
end
|
|
401
397
|
end
|
|
402
398
|
|
|
403
399
|
public
|
|
404
400
|
|
|
405
|
-
desc
|
|
401
|
+
desc 'profiles', 'List provisioning profiles (advanced - only needed for manual signing)'
|
|
406
402
|
long_desc <<~DESC
|
|
407
403
|
List all provisioning profiles in your organization.
|
|
408
|
-
|
|
404
|
+
|
|
409
405
|
WHEN DO YOU NEED THIS?
|
|
410
|
-
|
|
406
|
+
|
|
411
407
|
For Automatic Signing (Most Users):
|
|
412
408
|
❌ You DON'T need this - Xcode handles everything
|
|
413
|
-
|
|
409
|
+
|
|
414
410
|
For Manual Signing (Advanced):
|
|
415
411
|
✅ View available profiles
|
|
416
412
|
✅ Check expiration dates
|
|
417
413
|
✅ Get profile IDs for download/delete
|
|
418
|
-
|
|
414
|
+
|
|
419
415
|
EXAMPLES:
|
|
420
|
-
|
|
416
|
+
|
|
421
417
|
# List all profiles
|
|
422
418
|
mysigner profiles
|
|
423
|
-
|
|
419
|
+
#{' '}
|
|
424
420
|
# Filter by type
|
|
425
421
|
mysigner profiles --type APP_STORE
|
|
426
422
|
mysigner profiles --type DEVELOPMENT
|
|
427
|
-
|
|
423
|
+
#{' '}
|
|
428
424
|
# Filter by status
|
|
429
425
|
mysigner profiles --status EXPIRED
|
|
430
|
-
|
|
426
|
+
#{' '}
|
|
431
427
|
# Search by name
|
|
432
428
|
mysigner profiles --search "MyApp"
|
|
433
|
-
|
|
429
|
+
|
|
434
430
|
NOTE: Most users can skip this and just run 'mysigner build'
|
|
435
431
|
DESC
|
|
436
|
-
method_option :type, type: :string, aliases: '-t',
|
|
432
|
+
method_option :type, type: :string, aliases: '-t',
|
|
433
|
+
desc: 'Filter by type (DEVELOPMENT, AD_HOC, APP_STORE, ENTERPRISE)'
|
|
437
434
|
method_option :status, type: :string, aliases: '-s', desc: 'Filter by status (ACTIVE, EXPIRED, INVALID)'
|
|
438
435
|
method_option :search, type: :string, aliases: '-q', desc: 'Search by name or identifier'
|
|
439
436
|
method_option :page, type: :numeric, default: 1, desc: 'Page number'
|
|
@@ -442,8 +439,8 @@ module Mysigner
|
|
|
442
439
|
config = load_config
|
|
443
440
|
client = create_client(config)
|
|
444
441
|
|
|
445
|
-
say
|
|
446
|
-
say
|
|
442
|
+
say '📄 Provisioning Profiles', :cyan
|
|
443
|
+
say ''
|
|
447
444
|
|
|
448
445
|
# Build query params
|
|
449
446
|
params = {
|
|
@@ -460,9 +457,9 @@ module Mysigner
|
|
|
460
457
|
pagination = response[:data]['pagination']
|
|
461
458
|
|
|
462
459
|
if profiles.empty?
|
|
463
|
-
say
|
|
464
|
-
say
|
|
465
|
-
say
|
|
460
|
+
say 'No profiles found', :yellow
|
|
461
|
+
say ''
|
|
462
|
+
say 'Tip: Profiles are created automatically when you request code signing', :yellow
|
|
466
463
|
return
|
|
467
464
|
end
|
|
468
465
|
|
|
@@ -475,22 +472,20 @@ module Mysigner
|
|
|
475
472
|
say " ID: #{profile['id']} | Type: #{profile['profile_type'] || 'N/A'}"
|
|
476
473
|
say " Bundle ID: #{profile['bundle_id_identifier'] || 'N/A'}"
|
|
477
474
|
say " Status: #{profile['state'] || 'UNKNOWN'}"
|
|
478
|
-
|
|
475
|
+
|
|
479
476
|
if profile['expires_at']
|
|
480
477
|
expires = Time.parse(profile['expires_at']).strftime('%Y-%m-%d')
|
|
481
478
|
say " Expires: #{expires}"
|
|
482
479
|
end
|
|
483
|
-
|
|
484
|
-
say
|
|
480
|
+
|
|
481
|
+
say ''
|
|
485
482
|
end
|
|
486
483
|
|
|
487
484
|
# Show pagination
|
|
488
485
|
if pagination
|
|
489
486
|
say "Page #{pagination['page']} of #{pagination['total_pages']} (#{pagination['total']} total)", :yellow
|
|
490
487
|
|
|
491
|
-
if pagination['page'] < pagination['total_pages']
|
|
492
|
-
say "Run with --page #{pagination['page'] + 1} to see more", :yellow
|
|
493
|
-
end
|
|
488
|
+
say "Run with --page #{pagination['page'] + 1} to see more", :yellow if pagination['page'] < pagination['total_pages']
|
|
494
489
|
end
|
|
495
490
|
rescue Mysigner::ClientError => e
|
|
496
491
|
error "Failed to fetch profiles: #{e.message}"
|
|
@@ -498,68 +493,68 @@ module Mysigner
|
|
|
498
493
|
end
|
|
499
494
|
end
|
|
500
495
|
|
|
501
|
-
desc
|
|
496
|
+
desc 'profile SUBCOMMAND', 'Download or delete profiles (advanced - only needed for manual signing)'
|
|
502
497
|
long_desc <<~DESC
|
|
503
498
|
Manage provisioning profiles for code signing.
|
|
504
|
-
|
|
499
|
+
|
|
505
500
|
WHAT ARE PROVISIONING PROFILES?
|
|
506
|
-
|
|
501
|
+
|
|
507
502
|
Provisioning profiles are required for signing iOS apps. They link:
|
|
508
503
|
- Your signing certificate
|
|
509
504
|
- Your App ID (bundle ID)
|
|
510
505
|
- Authorized devices (for development/ad-hoc)
|
|
511
|
-
|
|
506
|
+
|
|
512
507
|
WHEN DO YOU NEED THIS?
|
|
513
|
-
|
|
508
|
+
|
|
514
509
|
For Automatic Signing (Most Users):
|
|
515
510
|
❌ You DON'T need these commands
|
|
516
511
|
✅ Xcode handles profiles automatically
|
|
517
512
|
✅ Just run: mysigner build
|
|
518
|
-
|
|
513
|
+
|
|
519
514
|
For Manual Signing (Advanced):
|
|
520
515
|
✅ Download profiles from My Signer
|
|
521
516
|
✅ Install them to ~/Library/MobileDevice/Provisioning Profiles/
|
|
522
517
|
✅ Delete old/expired profiles
|
|
523
|
-
|
|
518
|
+
|
|
524
519
|
SUBCOMMANDS:
|
|
525
|
-
|
|
520
|
+
|
|
526
521
|
mysigner profile download ID [--output path]
|
|
527
522
|
Download a provisioning profile
|
|
528
|
-
|
|
523
|
+
#{' '}
|
|
529
524
|
mysigner profile delete ID
|
|
530
525
|
Delete a provisioning profile
|
|
531
|
-
|
|
526
|
+
|
|
532
527
|
HOW TO USE:
|
|
533
|
-
|
|
528
|
+
|
|
534
529
|
1. List available profiles:
|
|
535
530
|
mysigner profiles
|
|
536
|
-
|
|
531
|
+
|
|
537
532
|
2. Download a profile:
|
|
538
533
|
mysigner profile download 1
|
|
539
|
-
|
|
534
|
+
|
|
540
535
|
3. Install it (double-click or manual):
|
|
541
536
|
open Profile_Name.mobileprovision
|
|
542
537
|
# Or: cp *.mobileprovision ~/Library/MobileDevice/Provisioning\\ Profiles/
|
|
543
|
-
|
|
538
|
+
|
|
544
539
|
EXAMPLES:
|
|
545
|
-
|
|
540
|
+
|
|
546
541
|
# Download profile ID 1
|
|
547
542
|
mysigner profile download 1
|
|
548
|
-
|
|
543
|
+
#{' '}
|
|
549
544
|
# Download to specific location
|
|
550
545
|
mysigner profile download 1 --output ~/Desktop/MyProfile.mobileprovision
|
|
551
|
-
|
|
546
|
+
#{' '}
|
|
552
547
|
# Delete expired profile
|
|
553
548
|
mysigner profile delete 5
|
|
554
|
-
|
|
549
|
+
#{' '}
|
|
555
550
|
# List all profiles
|
|
556
551
|
mysigner profiles
|
|
557
|
-
|
|
552
|
+
#{' '}
|
|
558
553
|
# Filter by type
|
|
559
554
|
mysigner profiles --type APP_STORE
|
|
560
|
-
|
|
555
|
+
|
|
561
556
|
NOTES:
|
|
562
|
-
|
|
557
|
+
|
|
563
558
|
• Most users with Automatic signing don't need this
|
|
564
559
|
• Manual signing wizard tries to auto-install profiles
|
|
565
560
|
• Profiles expire after 1 year and must be regenerated
|
|
@@ -574,13 +569,13 @@ module Mysigner
|
|
|
574
569
|
case action
|
|
575
570
|
when 'download'
|
|
576
571
|
if args.empty?
|
|
577
|
-
error
|
|
578
|
-
say
|
|
579
|
-
say
|
|
580
|
-
say
|
|
581
|
-
say
|
|
572
|
+
error 'Usage: mysigner profile download ID [--output path.mobileprovision]'
|
|
573
|
+
say ''
|
|
574
|
+
say 'Example: mysigner profile download 1', :yellow
|
|
575
|
+
say ''
|
|
576
|
+
say '💡 To get profile IDs:', :cyan
|
|
582
577
|
say " Run 'mysigner profiles' to see all profiles with their IDs", :cyan
|
|
583
|
-
say
|
|
578
|
+
say ''
|
|
584
579
|
say "Note: Most users with Automatic signing don't need this", :yellow
|
|
585
580
|
say "Run 'mysigner help profile' for more info", :cyan
|
|
586
581
|
exit 1
|
|
@@ -588,8 +583,8 @@ module Mysigner
|
|
|
588
583
|
|
|
589
584
|
profile_id = args[0]
|
|
590
585
|
|
|
591
|
-
say
|
|
592
|
-
say
|
|
586
|
+
say '📄 Downloading profile...', :cyan
|
|
587
|
+
say ''
|
|
593
588
|
|
|
594
589
|
begin
|
|
595
590
|
# Get profile details first
|
|
@@ -598,38 +593,38 @@ module Mysigner
|
|
|
598
593
|
|
|
599
594
|
# Determine output path
|
|
600
595
|
output_path = if options[:output]
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
596
|
+
options[:output]
|
|
597
|
+
else
|
|
598
|
+
# Use profile name, sanitize it for filename
|
|
599
|
+
name = profile['name'] || "profile_#{profile['id']}"
|
|
600
|
+
filename = name.gsub(/[^0-9A-Za-z.-]/, '_')
|
|
601
|
+
"#{filename}.mobileprovision"
|
|
602
|
+
end
|
|
608
603
|
|
|
609
604
|
# Download the profile content using the client's connection with auth
|
|
610
605
|
download_url = "/api/v1/organizations/#{config.current_organization_id}/profiles/#{profile_id}/download"
|
|
611
|
-
|
|
612
|
-
say
|
|
613
|
-
|
|
606
|
+
|
|
607
|
+
say 'Fetching profile content...', :yellow
|
|
608
|
+
|
|
614
609
|
# Use Faraday directly with proper auth for binary download
|
|
615
610
|
conn = Faraday.new(url: config.api_url) do |f|
|
|
616
611
|
f.request :authorization, 'Bearer', config.api_token
|
|
617
612
|
f.headers['X-User-Email'] = config.user_email if config.user_email
|
|
618
613
|
f.adapter Faraday.default_adapter
|
|
619
614
|
end
|
|
620
|
-
|
|
615
|
+
|
|
621
616
|
response = conn.get(download_url) do |req|
|
|
622
|
-
req.options.timeout = 30
|
|
623
|
-
req.options.open_timeout = 10
|
|
617
|
+
req.options.timeout = 30 # 30 second timeout
|
|
618
|
+
req.options.open_timeout = 10 # 10 second connection timeout
|
|
624
619
|
end
|
|
625
|
-
|
|
620
|
+
|
|
626
621
|
unless response.success?
|
|
627
622
|
# Check if it's a JSON error response
|
|
628
623
|
if response.headers['content-type']&.include?('json')
|
|
629
624
|
begin
|
|
630
625
|
error_data = JSON.parse(response.body)
|
|
631
626
|
error "Download failed: #{error_data['message'] || error_data['error']}"
|
|
632
|
-
rescue
|
|
627
|
+
rescue StandardError
|
|
633
628
|
error "Download failed with status #{response.status}"
|
|
634
629
|
end
|
|
635
630
|
else
|
|
@@ -641,62 +636,62 @@ module Mysigner
|
|
|
641
636
|
# Write binary content directly to file
|
|
642
637
|
File.binwrite(output_path, response.body)
|
|
643
638
|
|
|
644
|
-
say
|
|
645
|
-
say
|
|
646
|
-
say
|
|
639
|
+
say '✓ Profile downloaded successfully!', :green
|
|
640
|
+
say ''
|
|
641
|
+
say 'Details:', :bold
|
|
647
642
|
say " Name: #{profile['name']}"
|
|
648
643
|
say " Type: #{profile['profile_type'] || 'N/A'}"
|
|
649
644
|
say " Bundle ID: #{profile['bundle_id_identifier'] || 'N/A'}"
|
|
650
645
|
say " Status: #{profile['state'] || 'UNKNOWN'}"
|
|
651
646
|
say " File: #{output_path}"
|
|
652
|
-
say
|
|
647
|
+
say ''
|
|
653
648
|
say "File size: #{response.body.bytesize} bytes", :yellow
|
|
654
649
|
rescue Mysigner::NotFoundError
|
|
655
650
|
error "Profile not found with ID: #{profile_id}"
|
|
656
|
-
say
|
|
657
|
-
say
|
|
658
|
-
say
|
|
659
|
-
say
|
|
660
|
-
say
|
|
661
|
-
say
|
|
662
|
-
say
|
|
651
|
+
say ''
|
|
652
|
+
say '💡 Profile Not Found: How to fix', :cyan
|
|
653
|
+
say ''
|
|
654
|
+
say ' → List available profiles: mysigner profiles', :yellow
|
|
655
|
+
say ' → Sync from Apple: mysigner sync ios', :yellow
|
|
656
|
+
say ' → Check ID is correct (IDs are numeric)', :yellow
|
|
657
|
+
say ''
|
|
663
658
|
exit 1
|
|
664
659
|
rescue Mysigner::ClientError => e
|
|
665
660
|
error "Failed to download profile: #{e.message}"
|
|
666
|
-
say
|
|
667
|
-
say
|
|
668
|
-
say
|
|
669
|
-
say
|
|
670
|
-
say
|
|
671
|
-
say
|
|
672
|
-
say
|
|
673
|
-
exit 1
|
|
674
|
-
rescue => e
|
|
661
|
+
say ''
|
|
662
|
+
say '💡 Download Failed: Try these steps', :cyan
|
|
663
|
+
say ''
|
|
664
|
+
say ' → Check your network connection', :yellow
|
|
665
|
+
say ' → Verify API token is valid: mysigner status', :yellow
|
|
666
|
+
say ' → Re-authenticate if needed: mysigner login', :yellow
|
|
667
|
+
say ''
|
|
668
|
+
exit 1
|
|
669
|
+
rescue StandardError => e
|
|
675
670
|
error "Failed to save file: #{e.message}"
|
|
676
|
-
say
|
|
677
|
-
say
|
|
678
|
-
say
|
|
679
|
-
say
|
|
680
|
-
say
|
|
681
|
-
say
|
|
682
|
-
say
|
|
671
|
+
say ''
|
|
672
|
+
say '💡 File Save Failed: Check these', :cyan
|
|
673
|
+
say ''
|
|
674
|
+
say ' → Verify you have write permissions to the directory', :yellow
|
|
675
|
+
say ' → Check disk space is available', :yellow
|
|
676
|
+
say ' → Try specifying a different output path with --output', :yellow
|
|
677
|
+
say ''
|
|
683
678
|
exit 1
|
|
684
679
|
end
|
|
685
680
|
when 'delete'
|
|
686
681
|
if args.empty?
|
|
687
|
-
error
|
|
688
|
-
say
|
|
689
|
-
say
|
|
690
|
-
say
|
|
691
|
-
say
|
|
682
|
+
error 'Usage: mysigner profile delete ID'
|
|
683
|
+
say ''
|
|
684
|
+
say 'Example: mysigner profile delete 5', :yellow
|
|
685
|
+
say ''
|
|
686
|
+
say '💡 To get profile IDs:', :cyan
|
|
692
687
|
say " Run 'mysigner profiles' to see all profiles with their IDs", :cyan
|
|
693
688
|
exit 1
|
|
694
689
|
end
|
|
695
690
|
|
|
696
691
|
profile_id = args[0]
|
|
697
692
|
|
|
698
|
-
say
|
|
699
|
-
say
|
|
693
|
+
say '📄 Deleting profile...', :cyan
|
|
694
|
+
say ''
|
|
700
695
|
|
|
701
696
|
begin
|
|
702
697
|
# Get profile details first
|
|
@@ -704,18 +699,18 @@ module Mysigner
|
|
|
704
699
|
profile = response[:data]
|
|
705
700
|
|
|
706
701
|
# Confirm deletion
|
|
707
|
-
say
|
|
702
|
+
say 'You are about to delete:', :yellow
|
|
708
703
|
say " Name: #{profile['name']}"
|
|
709
704
|
say " Type: #{profile['profile_type']}"
|
|
710
705
|
say " Bundle ID: #{profile['bundle_id_identifier'] || 'N/A'}"
|
|
711
|
-
say
|
|
706
|
+
say ''
|
|
712
707
|
|
|
713
|
-
if yes?(
|
|
708
|
+
if yes?('Are you sure you want to delete this profile? (y/n)')
|
|
714
709
|
client.delete("/api/v1/organizations/#{config.current_organization_id}/profiles/#{profile_id}")
|
|
715
|
-
say
|
|
716
|
-
say
|
|
710
|
+
say ''
|
|
711
|
+
say '✓ Profile deleted successfully!', :green
|
|
717
712
|
else
|
|
718
|
-
say
|
|
713
|
+
say 'Deletion cancelled', :yellow
|
|
719
714
|
end
|
|
720
715
|
rescue Mysigner::NotFoundError
|
|
721
716
|
error "Profile not found with ID: #{profile_id}"
|
|
@@ -728,12 +723,12 @@ module Mysigner
|
|
|
728
723
|
invoke :help, ['profile']
|
|
729
724
|
else
|
|
730
725
|
error "Unknown action: #{action}"
|
|
731
|
-
say
|
|
726
|
+
say 'Available actions: download, delete, help', :yellow
|
|
732
727
|
exit 1
|
|
733
728
|
end
|
|
734
729
|
end
|
|
735
730
|
|
|
736
|
-
desc
|
|
731
|
+
desc 'certificates', 'List signing certificates from App Store Connect'
|
|
737
732
|
method_option :type, type: :string, aliases: '-p', desc: 'Filter by type (DEVELOPMENT, DISTRIBUTION)'
|
|
738
733
|
method_option :status, type: :string, aliases: '-s', desc: 'Filter by status (ACTIVE, EXPIRED, REVOKED)'
|
|
739
734
|
method_option :search, type: :string, aliases: '-q', desc: 'Search by name'
|
|
@@ -743,8 +738,8 @@ module Mysigner
|
|
|
743
738
|
config = load_config
|
|
744
739
|
client = create_client(config)
|
|
745
740
|
|
|
746
|
-
say
|
|
747
|
-
say
|
|
741
|
+
say '🔐 Signing Certificates', :cyan
|
|
742
|
+
say ''
|
|
748
743
|
|
|
749
744
|
# Build query params
|
|
750
745
|
params = {
|
|
@@ -756,14 +751,15 @@ module Mysigner
|
|
|
756
751
|
params[:q] = options[:search] if options[:search]
|
|
757
752
|
|
|
758
753
|
begin
|
|
759
|
-
response = client.get("/api/v1/organizations/#{config.current_organization_id}/certificates",
|
|
754
|
+
response = client.get("/api/v1/organizations/#{config.current_organization_id}/certificates",
|
|
755
|
+
params: params)
|
|
760
756
|
certificates = response[:data]['certificates']
|
|
761
757
|
pagination = response[:data]['pagination']
|
|
762
758
|
|
|
763
759
|
if certificates.empty?
|
|
764
|
-
say
|
|
765
|
-
say
|
|
766
|
-
say
|
|
760
|
+
say 'No certificates found', :yellow
|
|
761
|
+
say ''
|
|
762
|
+
say 'Tip: Certificates are synced automatically from App Store Connect', :yellow
|
|
767
763
|
return
|
|
768
764
|
end
|
|
769
765
|
|
|
@@ -771,27 +767,25 @@ module Mysigner
|
|
|
771
767
|
certificates.each do |cert|
|
|
772
768
|
status_icon = cert['status'] == 'ACTIVE' ? '✓' : '✗'
|
|
773
769
|
status_color = cert['status'] == 'ACTIVE' ? :green : :red
|
|
774
|
-
|
|
770
|
+
|
|
775
771
|
say " #{status_icon} #{cert['name']}", status_color
|
|
776
772
|
say " ID: #{cert['id']} | Type: #{cert['certificate_type'] || 'N/A'}"
|
|
777
773
|
say " Serial: #{cert['serial_number'] || 'N/A'}"
|
|
778
774
|
say " Status: #{cert['status'] || 'UNKNOWN'}"
|
|
779
|
-
|
|
775
|
+
|
|
780
776
|
if cert['expires_at']
|
|
781
777
|
expires = Time.parse(cert['expires_at']).strftime('%Y-%m-%d')
|
|
782
778
|
say " Expires: #{expires}"
|
|
783
779
|
end
|
|
784
|
-
|
|
785
|
-
say
|
|
780
|
+
|
|
781
|
+
say ''
|
|
786
782
|
end
|
|
787
783
|
|
|
788
784
|
# Show pagination
|
|
789
785
|
if pagination
|
|
790
786
|
say "Page #{pagination['page']} of #{pagination['total_pages']} (#{pagination['total']} total)", :yellow
|
|
791
787
|
|
|
792
|
-
if pagination['page'] < pagination['total_pages']
|
|
793
|
-
say "Run with --page #{pagination['page'] + 1} to see more", :yellow
|
|
794
|
-
end
|
|
788
|
+
say "Run with --page #{pagination['page'] + 1} to see more", :yellow if pagination['page'] < pagination['total_pages']
|
|
795
789
|
end
|
|
796
790
|
rescue Mysigner::ClientError => e
|
|
797
791
|
error "Failed to fetch certificates: #{e.message}"
|
|
@@ -799,12 +793,12 @@ module Mysigner
|
|
|
799
793
|
end
|
|
800
794
|
end
|
|
801
795
|
|
|
802
|
-
desc
|
|
796
|
+
desc 'certificate ACTION', 'Check local keychain or download certificates (check, download)'
|
|
803
797
|
long_desc <<~DESC
|
|
804
798
|
Actions:
|
|
805
799
|
check - Check certificates installed in your Mac's Keychain (not API)
|
|
806
800
|
download ID - Download a certificate from My Signer API
|
|
807
|
-
|
|
801
|
+
|
|
808
802
|
Note: 'check' scans your LOCAL Keychain, not certificates in My Signer API.
|
|
809
803
|
Use 'mysigner certificates' to see API certificates.
|
|
810
804
|
DESC
|
|
@@ -816,111 +810,114 @@ module Mysigner
|
|
|
816
810
|
case action
|
|
817
811
|
when 'check'
|
|
818
812
|
require_relative '../signing/certificate_checker'
|
|
819
|
-
|
|
820
|
-
say
|
|
821
|
-
say
|
|
822
|
-
|
|
813
|
+
|
|
814
|
+
say '🔍 Checking local certificates...', :cyan
|
|
815
|
+
say ''
|
|
816
|
+
|
|
823
817
|
checker = Signing::CertificateChecker.new
|
|
824
|
-
|
|
818
|
+
|
|
825
819
|
begin
|
|
826
820
|
certificates = checker.check!
|
|
827
|
-
|
|
821
|
+
|
|
828
822
|
if certificates.empty?
|
|
829
|
-
say
|
|
830
|
-
say
|
|
831
|
-
say
|
|
832
|
-
say
|
|
833
|
-
say
|
|
834
|
-
say
|
|
835
|
-
say
|
|
836
|
-
say
|
|
837
|
-
say
|
|
838
|
-
say
|
|
839
|
-
say
|
|
840
|
-
say
|
|
841
|
-
say
|
|
823
|
+
say 'No code signing certificates found in local Keychain', :yellow
|
|
824
|
+
say ''
|
|
825
|
+
say '⚠️ Important:', :yellow
|
|
826
|
+
say ' This command checks certificates INSTALLED ON YOUR MAC.', :white
|
|
827
|
+
say ' Certificates in My Signer API are not automatically installed locally.', :white
|
|
828
|
+
say ''
|
|
829
|
+
say 'To install certificates:', :cyan
|
|
830
|
+
say ' 1. List certificates in My Signer: mysigner certificates', :white
|
|
831
|
+
say ' 2. Download one: mysigner certificate download <ID>', :white
|
|
832
|
+
say ' 3. Double-click the .cer file to install in Keychain', :white
|
|
833
|
+
say ''
|
|
834
|
+
say 'Or download from Apple Developer:', :cyan
|
|
835
|
+
say ' https://developer.apple.com/account/resources/certificates/list', :white
|
|
842
836
|
return
|
|
843
837
|
end
|
|
844
|
-
|
|
838
|
+
|
|
845
839
|
# Group by status
|
|
846
840
|
by_status = checker.by_status
|
|
847
|
-
|
|
841
|
+
|
|
848
842
|
# Show valid certificates
|
|
849
843
|
if by_status[:valid].any?
|
|
850
844
|
say "✓ Valid Certificates (#{by_status[:valid].count})", :green
|
|
851
|
-
say
|
|
845
|
+
say ''
|
|
852
846
|
by_status[:valid].each do |cert|
|
|
853
847
|
say " #{cert[:name]}", :green
|
|
854
848
|
say " Type: #{cert[:type]}"
|
|
855
849
|
say " Team: #{cert[:team_id] || 'Unknown'}"
|
|
856
|
-
say " Expires: #{cert[:expires_at].strftime('%Y-%m-%d')} (#{cert[:days_until_expiry]} days)",
|
|
857
|
-
|
|
850
|
+
say " Expires: #{cert[:expires_at].strftime('%Y-%m-%d')} (#{cert[:days_until_expiry]} days)",
|
|
851
|
+
:white
|
|
852
|
+
say ''
|
|
858
853
|
end
|
|
859
854
|
end
|
|
860
|
-
|
|
855
|
+
|
|
861
856
|
# Show expiring soon certificates
|
|
862
857
|
if by_status[:expiring_soon].any?
|
|
863
858
|
say "⚠️ Expiring Soon (#{by_status[:expiring_soon].count})", :yellow
|
|
864
|
-
say
|
|
859
|
+
say ''
|
|
865
860
|
by_status[:expiring_soon].each do |cert|
|
|
866
861
|
say " #{cert[:name]}", :yellow
|
|
867
862
|
say " Type: #{cert[:type]}"
|
|
868
863
|
say " Team: #{cert[:team_id] || 'Unknown'}"
|
|
869
|
-
say " Expires: #{cert[:expires_at].strftime('%Y-%m-%d')} (#{cert[:days_until_expiry]} days)",
|
|
870
|
-
|
|
864
|
+
say " Expires: #{cert[:expires_at].strftime('%Y-%m-%d')} (#{cert[:days_until_expiry]} days)",
|
|
865
|
+
:yellow
|
|
866
|
+
say ''
|
|
871
867
|
end
|
|
872
|
-
say
|
|
873
|
-
say
|
|
868
|
+
say 'Renew these certificates soon to avoid build failures!', :yellow
|
|
869
|
+
say ''
|
|
874
870
|
end
|
|
875
|
-
|
|
871
|
+
|
|
876
872
|
# Show expired certificates
|
|
877
873
|
if by_status[:expired].any?
|
|
878
874
|
say "✗ Expired Certificates (#{by_status[:expired].count})", :red
|
|
879
|
-
say
|
|
875
|
+
say ''
|
|
880
876
|
by_status[:expired].each do |cert|
|
|
881
877
|
say " #{cert[:name]}", :red
|
|
882
878
|
say " Type: #{cert[:type]}"
|
|
883
879
|
say " Team: #{cert[:team_id] || 'Unknown'}"
|
|
884
|
-
say " Expired: #{cert[:expires_at].strftime('%Y-%m-%d')} (#{cert[:days_until_expiry].abs} days ago)",
|
|
885
|
-
|
|
880
|
+
say " Expired: #{cert[:expires_at].strftime('%Y-%m-%d')} (#{cert[:days_until_expiry].abs} days ago)",
|
|
881
|
+
:red
|
|
882
|
+
say ''
|
|
886
883
|
end
|
|
887
|
-
say
|
|
888
|
-
say
|
|
889
|
-
say
|
|
884
|
+
say 'These certificates will cause build failures. Renew them at:', :red
|
|
885
|
+
say ' https://developer.apple.com/account/resources/certificates/list', :white
|
|
886
|
+
say ''
|
|
890
887
|
end
|
|
891
|
-
|
|
888
|
+
|
|
892
889
|
# Summary
|
|
893
|
-
say
|
|
894
|
-
say "Total: #{certificates.count} certificate#{
|
|
890
|
+
say '─' * 80, :cyan
|
|
891
|
+
say "Total: #{certificates.count} certificate#{'s' unless certificates.one?} installed locally",
|
|
892
|
+
:cyan
|
|
895
893
|
if checker.has_issues?
|
|
896
|
-
say
|
|
894
|
+
say 'Status: ⚠️ Action required', :yellow
|
|
897
895
|
else
|
|
898
|
-
say
|
|
896
|
+
say 'Status: ✓ All certificates valid', :green
|
|
899
897
|
end
|
|
900
|
-
say
|
|
901
|
-
say
|
|
902
|
-
say
|
|
903
|
-
|
|
898
|
+
say ''
|
|
899
|
+
say '💡 Tip: These are certificates INSTALLED ON YOUR MAC.', :cyan
|
|
900
|
+
say ' To see all certificates in My Signer API, run: mysigner certificates', :white
|
|
904
901
|
rescue Signing::CertificateChecker::CheckError => e
|
|
905
902
|
error "Certificate check failed: #{e.message}"
|
|
906
|
-
say
|
|
907
|
-
say
|
|
908
|
-
say
|
|
909
|
-
say
|
|
910
|
-
say
|
|
903
|
+
say ''
|
|
904
|
+
say 'This usually means:', :yellow
|
|
905
|
+
say ' • Keychain is locked', :white
|
|
906
|
+
say ' • No certificates installed', :white
|
|
907
|
+
say ' • Security command not available', :white
|
|
911
908
|
exit 1
|
|
912
909
|
end
|
|
913
|
-
|
|
910
|
+
|
|
914
911
|
when 'download'
|
|
915
912
|
if args.empty?
|
|
916
|
-
error
|
|
913
|
+
error 'Usage: mysigner certificate download ID [--output path.cer]'
|
|
917
914
|
exit 1
|
|
918
915
|
end
|
|
919
916
|
|
|
920
917
|
certificate_id = args[0]
|
|
921
918
|
|
|
922
|
-
say
|
|
923
|
-
say
|
|
919
|
+
say '🔐 Downloading certificate...', :cyan
|
|
920
|
+
say ''
|
|
924
921
|
|
|
925
922
|
begin
|
|
926
923
|
# Get certificate details first
|
|
@@ -929,38 +926,38 @@ module Mysigner
|
|
|
929
926
|
|
|
930
927
|
# Determine output path
|
|
931
928
|
output_path = if options[:output]
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
929
|
+
options[:output]
|
|
930
|
+
else
|
|
931
|
+
# Use certificate name, sanitize it for filename
|
|
932
|
+
name = certificate['name'] || "certificate_#{certificate['id']}"
|
|
933
|
+
filename = name.gsub(/[^0-9A-Za-z.-]/, '_')
|
|
934
|
+
"#{filename}.cer"
|
|
935
|
+
end
|
|
939
936
|
|
|
940
937
|
# Download the certificate content (binary response)
|
|
941
938
|
download_url = "/api/v1/organizations/#{config.current_organization_id}/certificates/#{certificate_id}/download"
|
|
942
|
-
|
|
943
|
-
say
|
|
944
|
-
|
|
939
|
+
|
|
940
|
+
say 'Fetching certificate content...', :yellow
|
|
941
|
+
|
|
945
942
|
# Use Faraday directly with proper auth for binary download
|
|
946
943
|
conn = Faraday.new(url: config.api_url) do |f|
|
|
947
944
|
f.request :authorization, 'Bearer', config.api_token
|
|
948
945
|
f.headers['X-User-Email'] = config.user_email if config.user_email
|
|
949
946
|
f.adapter Faraday.default_adapter
|
|
950
947
|
end
|
|
951
|
-
|
|
948
|
+
|
|
952
949
|
response = conn.get(download_url) do |req|
|
|
953
|
-
req.options.timeout = 30
|
|
954
|
-
req.options.open_timeout = 10
|
|
950
|
+
req.options.timeout = 30 # 30 second timeout
|
|
951
|
+
req.options.open_timeout = 10 # 10 second connection timeout
|
|
955
952
|
end
|
|
956
|
-
|
|
953
|
+
|
|
957
954
|
unless response.success?
|
|
958
955
|
# Check if it's a JSON error response
|
|
959
956
|
if response.headers['content-type']&.include?('json')
|
|
960
957
|
begin
|
|
961
958
|
error_data = JSON.parse(response.body)
|
|
962
959
|
error "Download failed: #{error_data['message'] || error_data['error']}"
|
|
963
|
-
rescue
|
|
960
|
+
rescue StandardError
|
|
964
961
|
error "Download failed with status #{response.status}"
|
|
965
962
|
end
|
|
966
963
|
else
|
|
@@ -972,59 +969,59 @@ module Mysigner
|
|
|
972
969
|
# Write binary content directly to file
|
|
973
970
|
File.binwrite(output_path, response.body)
|
|
974
971
|
|
|
975
|
-
say
|
|
976
|
-
say
|
|
977
|
-
say
|
|
972
|
+
say '✓ Certificate downloaded successfully!', :green
|
|
973
|
+
say ''
|
|
974
|
+
say 'Details:', :bold
|
|
978
975
|
say " Name: #{certificate['name']}"
|
|
979
976
|
say " Type: #{certificate['certificate_type'] || 'N/A'}"
|
|
980
977
|
say " Serial: #{certificate['serial_number'] || 'N/A'}"
|
|
981
978
|
say " Status: #{certificate['status'] || 'UNKNOWN'}"
|
|
982
979
|
say " File: #{output_path}"
|
|
983
|
-
say
|
|
980
|
+
say ''
|
|
984
981
|
say "File size: #{response.body.bytesize} bytes", :yellow
|
|
985
982
|
rescue Mysigner::NotFoundError
|
|
986
983
|
error "Certificate not found with ID: #{certificate_id}"
|
|
987
|
-
say
|
|
988
|
-
say
|
|
989
|
-
say
|
|
990
|
-
say
|
|
991
|
-
say
|
|
992
|
-
say
|
|
993
|
-
say
|
|
984
|
+
say ''
|
|
985
|
+
say '💡 Certificate Not Found: How to fix', :cyan
|
|
986
|
+
say ''
|
|
987
|
+
say ' → List available certificates: mysigner certificates', :yellow
|
|
988
|
+
say ' → Sync from Apple: mysigner sync ios', :yellow
|
|
989
|
+
say ' → Check the ID is correct (IDs are numeric)', :yellow
|
|
990
|
+
say ''
|
|
994
991
|
exit 1
|
|
995
992
|
rescue Mysigner::ClientError => e
|
|
996
993
|
error "Failed to download certificate: #{e.message}"
|
|
997
|
-
say
|
|
998
|
-
say
|
|
999
|
-
say
|
|
1000
|
-
say
|
|
1001
|
-
say
|
|
1002
|
-
say
|
|
1003
|
-
say
|
|
1004
|
-
exit 1
|
|
1005
|
-
rescue => e
|
|
994
|
+
say ''
|
|
995
|
+
say '💡 Download Failed: Try these steps', :cyan
|
|
996
|
+
say ''
|
|
997
|
+
say ' → Check your network connection', :yellow
|
|
998
|
+
say ' → Verify API token is valid: mysigner status', :yellow
|
|
999
|
+
say ' → Re-authenticate if needed: mysigner login', :yellow
|
|
1000
|
+
say ''
|
|
1001
|
+
exit 1
|
|
1002
|
+
rescue StandardError => e
|
|
1006
1003
|
error "Failed to save file: #{e.message}"
|
|
1007
|
-
say
|
|
1008
|
-
say
|
|
1009
|
-
say
|
|
1010
|
-
say
|
|
1011
|
-
say
|
|
1012
|
-
say
|
|
1013
|
-
say
|
|
1004
|
+
say ''
|
|
1005
|
+
say '💡 File Save Failed: Check these', :cyan
|
|
1006
|
+
say ''
|
|
1007
|
+
say ' → Verify you have write permissions to the directory', :yellow
|
|
1008
|
+
say ' → Check disk space is available', :yellow
|
|
1009
|
+
say ' → Try specifying a different output path with --output', :yellow
|
|
1010
|
+
say ''
|
|
1014
1011
|
exit 1
|
|
1015
1012
|
end
|
|
1016
1013
|
when 'help'
|
|
1017
1014
|
invoke :help, ['certificate']
|
|
1018
1015
|
else
|
|
1019
1016
|
error "Unknown action: #{action}"
|
|
1020
|
-
say
|
|
1017
|
+
say 'Available actions: check, download, help', :yellow
|
|
1021
1018
|
exit 1
|
|
1022
1019
|
end
|
|
1023
1020
|
end
|
|
1024
1021
|
|
|
1025
1022
|
# ==================== ANDROID KEYSTORES ====================
|
|
1026
1023
|
|
|
1027
|
-
desc
|
|
1024
|
+
desc 'keystore SUBCOMMAND', 'Manage Android keystores (list, upload, download, delete, activate)'
|
|
1028
1025
|
long_desc <<~DESC
|
|
1029
1026
|
Manage Android keystores for signing your apps.
|
|
1030
1027
|
|
|
@@ -1087,38 +1084,38 @@ module Mysigner
|
|
|
1087
1084
|
|
|
1088
1085
|
case action
|
|
1089
1086
|
when 'list'
|
|
1090
|
-
say
|
|
1091
|
-
say
|
|
1087
|
+
say '🔐 Android Keystores', :cyan
|
|
1088
|
+
say ''
|
|
1092
1089
|
|
|
1093
1090
|
keystores = manager.list(android_app_id: options[:app_id])
|
|
1094
1091
|
|
|
1095
1092
|
if keystores.empty?
|
|
1096
|
-
say
|
|
1097
|
-
say
|
|
1098
|
-
say
|
|
1093
|
+
say 'No keystores found', :yellow
|
|
1094
|
+
say ''
|
|
1095
|
+
say 'Upload a keystore with: mysigner keystore upload PATH', :yellow
|
|
1099
1096
|
return
|
|
1100
1097
|
end
|
|
1101
1098
|
|
|
1102
1099
|
keystores.each do |ks|
|
|
1103
1100
|
active_icon = ks['active'] ? '✓' : '○'
|
|
1104
1101
|
active_color = ks['active'] ? :green : :white
|
|
1105
|
-
|
|
1102
|
+
|
|
1106
1103
|
say " #{active_icon} #{ks['name']} (ID: #{ks['id']})", active_color
|
|
1107
1104
|
say " Key Alias: #{ks['key_alias'] || 'N/A'}"
|
|
1108
1105
|
say " App: #{ks['package_name']}" if ks['package_name']
|
|
1109
1106
|
say " Active: #{ks['active'] ? 'Yes' : 'No'}"
|
|
1110
|
-
say
|
|
1107
|
+
say ''
|
|
1111
1108
|
end
|
|
1112
1109
|
|
|
1113
1110
|
say "Total: #{keystores.count} keystore(s)", :yellow
|
|
1114
1111
|
|
|
1115
1112
|
when 'upload'
|
|
1116
1113
|
keystore_path = args[0]
|
|
1117
|
-
|
|
1114
|
+
|
|
1118
1115
|
unless keystore_path
|
|
1119
|
-
error
|
|
1120
|
-
say
|
|
1121
|
-
say
|
|
1116
|
+
error 'Usage: mysigner keystore upload PATH'
|
|
1117
|
+
say ''
|
|
1118
|
+
say 'Example: mysigner keystore upload ~/keys/release.jks', :yellow
|
|
1122
1119
|
exit 1
|
|
1123
1120
|
end
|
|
1124
1121
|
|
|
@@ -1127,16 +1124,16 @@ module Mysigner
|
|
|
1127
1124
|
exit 1
|
|
1128
1125
|
end
|
|
1129
1126
|
|
|
1130
|
-
say
|
|
1131
|
-
say
|
|
1127
|
+
say '🔐 Uploading keystore...', :cyan
|
|
1128
|
+
say ''
|
|
1132
1129
|
|
|
1133
1130
|
# Get keystore details
|
|
1134
1131
|
name = options[:name] || ask("Keystore name (e.g., 'Release Key'):")
|
|
1135
|
-
key_alias = options[:alias] || ask(
|
|
1136
|
-
password = ask(
|
|
1137
|
-
say
|
|
1138
|
-
key_password = ask(
|
|
1139
|
-
say
|
|
1132
|
+
key_alias = options[:alias] || ask('Key alias:')
|
|
1133
|
+
password = ask('Keystore password:', echo: false)
|
|
1134
|
+
say ''
|
|
1135
|
+
key_password = ask('Key password (press Enter if same as keystore):', echo: false)
|
|
1136
|
+
say ''
|
|
1140
1137
|
key_password = password if key_password.empty?
|
|
1141
1138
|
|
|
1142
1139
|
begin
|
|
@@ -1150,52 +1147,51 @@ module Mysigner
|
|
|
1150
1147
|
active: true
|
|
1151
1148
|
)
|
|
1152
1149
|
|
|
1153
|
-
say
|
|
1154
|
-
say
|
|
1155
|
-
say
|
|
1150
|
+
say '✓ Keystore uploaded successfully!', :green
|
|
1151
|
+
say ''
|
|
1152
|
+
say 'Details:', :bold
|
|
1156
1153
|
say " ID: #{result['id']}"
|
|
1157
1154
|
say " Name: #{result['name']}"
|
|
1158
1155
|
say " Key Alias: #{result['key_alias']}"
|
|
1159
1156
|
say " Active: #{result['active']}"
|
|
1160
|
-
say
|
|
1161
|
-
|
|
1157
|
+
say ''
|
|
1162
1158
|
rescue Signing::KeystoreManager::KeystoreError => e
|
|
1163
1159
|
error "Upload failed: #{e.message}"
|
|
1164
|
-
say
|
|
1165
|
-
say
|
|
1166
|
-
say
|
|
1167
|
-
say
|
|
1168
|
-
say
|
|
1169
|
-
say
|
|
1170
|
-
say
|
|
1171
|
-
say
|
|
1160
|
+
say ''
|
|
1161
|
+
say '💡 Keystore Upload Failed: Common issues', :cyan
|
|
1162
|
+
say ''
|
|
1163
|
+
say ' → Verify the keystore file is valid (.jks or .keystore)', :yellow
|
|
1164
|
+
say ' → Check keystore password is correct', :yellow
|
|
1165
|
+
say ' → Check key alias exists in the keystore', :yellow
|
|
1166
|
+
say ' → Verify key password is correct', :yellow
|
|
1167
|
+
say ''
|
|
1172
1168
|
say " Test with: keytool -list -keystore #{keystore_path}", :green
|
|
1173
|
-
say
|
|
1169
|
+
say ''
|
|
1174
1170
|
exit 1
|
|
1175
1171
|
rescue Mysigner::ClientError => e
|
|
1176
1172
|
error "API error: #{e.message}"
|
|
1177
|
-
say
|
|
1178
|
-
say
|
|
1179
|
-
say
|
|
1180
|
-
say
|
|
1181
|
-
say
|
|
1182
|
-
say
|
|
1183
|
-
say
|
|
1173
|
+
say ''
|
|
1174
|
+
say '💡 API Error: Try these steps', :cyan
|
|
1175
|
+
say ''
|
|
1176
|
+
say ' → Check your network connection', :yellow
|
|
1177
|
+
say ' → Verify API token is valid: mysigner status', :yellow
|
|
1178
|
+
say ' → Re-authenticate if needed: mysigner login', :yellow
|
|
1179
|
+
say ''
|
|
1184
1180
|
exit 1
|
|
1185
1181
|
end
|
|
1186
1182
|
|
|
1187
1183
|
when 'download'
|
|
1188
1184
|
keystore_id = args[0]
|
|
1189
|
-
|
|
1185
|
+
|
|
1190
1186
|
unless keystore_id
|
|
1191
|
-
error
|
|
1192
|
-
say
|
|
1187
|
+
error 'Usage: mysigner keystore download ID'
|
|
1188
|
+
say ''
|
|
1193
1189
|
say "Run 'mysigner keystore list' to see available IDs", :yellow
|
|
1194
1190
|
exit 1
|
|
1195
1191
|
end
|
|
1196
1192
|
|
|
1197
|
-
say
|
|
1198
|
-
say
|
|
1193
|
+
say '🔐 Downloading keystore...', :cyan
|
|
1194
|
+
say ''
|
|
1199
1195
|
|
|
1200
1196
|
begin
|
|
1201
1197
|
result = manager.download(keystore_id)
|
|
@@ -1206,110 +1202,109 @@ module Mysigner
|
|
|
1206
1202
|
result[:path] = options[:output]
|
|
1207
1203
|
end
|
|
1208
1204
|
|
|
1209
|
-
say
|
|
1210
|
-
say
|
|
1211
|
-
say
|
|
1205
|
+
say '✓ Keystore downloaded!', :green
|
|
1206
|
+
say ''
|
|
1207
|
+
say 'Details:', :bold
|
|
1212
1208
|
say " Name: #{result[:name]}"
|
|
1213
1209
|
say " Key Alias: #{result[:key_alias]}"
|
|
1214
1210
|
say " Path: #{result[:path]}"
|
|
1215
|
-
say
|
|
1216
|
-
say
|
|
1217
|
-
|
|
1211
|
+
say ''
|
|
1212
|
+
say '⚠️ Keep this file secure and backed up!', :yellow
|
|
1218
1213
|
rescue Signing::KeystoreManager::KeystoreNotFoundError => e
|
|
1219
1214
|
error "Keystore not found: #{e.message}"
|
|
1220
|
-
say
|
|
1221
|
-
say
|
|
1222
|
-
say
|
|
1223
|
-
say
|
|
1224
|
-
say
|
|
1225
|
-
say
|
|
1226
|
-
say
|
|
1215
|
+
say ''
|
|
1216
|
+
say '💡 Keystore Not Found: How to fix', :cyan
|
|
1217
|
+
say ''
|
|
1218
|
+
say ' → List available keystores: mysigner keystore list', :yellow
|
|
1219
|
+
say ' → Upload a keystore: mysigner keystore upload <path>', :yellow
|
|
1220
|
+
say ' → Check the ID is correct (IDs are numeric)', :yellow
|
|
1221
|
+
say ''
|
|
1227
1222
|
exit 1
|
|
1228
1223
|
rescue Signing::KeystoreManager::DownloadError => e
|
|
1229
1224
|
error "Download failed: #{e.message}"
|
|
1230
|
-
say
|
|
1231
|
-
say
|
|
1232
|
-
say
|
|
1233
|
-
say
|
|
1234
|
-
say
|
|
1235
|
-
say
|
|
1236
|
-
say
|
|
1225
|
+
say ''
|
|
1226
|
+
say '💡 Download Failed: Try these steps', :cyan
|
|
1227
|
+
say ''
|
|
1228
|
+
say ' → Check your network connection', :yellow
|
|
1229
|
+
say ' → Verify API token is valid: mysigner status', :yellow
|
|
1230
|
+
say ' → Re-authenticate if needed: mysigner login', :yellow
|
|
1231
|
+
say ''
|
|
1237
1232
|
exit 1
|
|
1238
1233
|
end
|
|
1239
1234
|
|
|
1240
1235
|
when 'delete'
|
|
1241
1236
|
keystore_id = args[0]
|
|
1242
|
-
|
|
1237
|
+
|
|
1243
1238
|
unless keystore_id
|
|
1244
|
-
error
|
|
1239
|
+
error 'Usage: mysigner keystore delete ID'
|
|
1245
1240
|
exit 1
|
|
1246
1241
|
end
|
|
1247
1242
|
|
|
1248
1243
|
# Get keystore details first
|
|
1249
1244
|
keystores = manager.list
|
|
1250
1245
|
keystore = keystores.find { |k| k['id'].to_s == keystore_id.to_s }
|
|
1251
|
-
|
|
1246
|
+
|
|
1252
1247
|
unless keystore
|
|
1253
1248
|
error "Keystore not found with ID: #{keystore_id}"
|
|
1254
|
-
say
|
|
1255
|
-
say
|
|
1256
|
-
say
|
|
1257
|
-
say
|
|
1258
|
-
say
|
|
1259
|
-
say
|
|
1249
|
+
say ''
|
|
1250
|
+
say '💡 Keystore Not Found: How to fix', :cyan
|
|
1251
|
+
say ''
|
|
1252
|
+
say ' → List available keystores: mysigner keystore list', :yellow
|
|
1253
|
+
say ' → Upload a keystore: mysigner keystore upload <path>', :yellow
|
|
1254
|
+
say ''
|
|
1260
1255
|
exit 1
|
|
1261
1256
|
end
|
|
1262
1257
|
|
|
1263
|
-
say
|
|
1258
|
+
say '⚠️ You are about to delete:', :yellow
|
|
1264
1259
|
say " Name: #{keystore['name']}"
|
|
1265
1260
|
say " Key Alias: #{keystore['key_alias']}"
|
|
1266
|
-
say
|
|
1261
|
+
say ''
|
|
1267
1262
|
|
|
1268
|
-
if yes?(
|
|
1263
|
+
if yes?('Are you sure? This cannot be undone. (y/n)')
|
|
1269
1264
|
begin
|
|
1270
1265
|
manager.delete(keystore_id)
|
|
1271
|
-
say
|
|
1272
|
-
say
|
|
1266
|
+
say ''
|
|
1267
|
+
say '✓ Keystore deleted', :green
|
|
1273
1268
|
rescue Mysigner::ClientError => e
|
|
1274
1269
|
error "Delete failed: #{e.message}"
|
|
1275
1270
|
exit 1
|
|
1276
1271
|
end
|
|
1277
1272
|
else
|
|
1278
|
-
say
|
|
1273
|
+
say 'Deletion cancelled', :yellow
|
|
1279
1274
|
end
|
|
1280
1275
|
|
|
1281
1276
|
when 'activate'
|
|
1282
1277
|
keystore_id = args[0]
|
|
1283
|
-
|
|
1278
|
+
|
|
1284
1279
|
unless keystore_id
|
|
1285
|
-
error
|
|
1280
|
+
error 'Usage: mysigner keystore activate ID'
|
|
1286
1281
|
exit 1
|
|
1287
1282
|
end
|
|
1288
1283
|
|
|
1289
|
-
say
|
|
1284
|
+
say '🔐 Activating keystore...', :cyan
|
|
1290
1285
|
|
|
1291
1286
|
begin
|
|
1292
1287
|
result = manager.activate(keystore_id)
|
|
1293
|
-
say
|
|
1294
|
-
say
|
|
1288
|
+
say '✓ Keystore activated!', :green
|
|
1289
|
+
say ''
|
|
1295
1290
|
say "#{result['name']} is now the default keystore", :cyan
|
|
1296
1291
|
rescue Mysigner::NotFoundError
|
|
1297
1292
|
error "Keystore not found with ID: #{keystore_id}"
|
|
1298
|
-
say
|
|
1299
|
-
say
|
|
1300
|
-
say
|
|
1301
|
-
say
|
|
1302
|
-
say
|
|
1303
|
-
say
|
|
1293
|
+
say ''
|
|
1294
|
+
say '💡 Keystore Not Found: How to fix', :cyan
|
|
1295
|
+
say ''
|
|
1296
|
+
say ' → List available keystores: mysigner keystore list', :yellow
|
|
1297
|
+
say ' → Upload a keystore: mysigner keystore upload <path>', :yellow
|
|
1298
|
+
say ''
|
|
1304
1299
|
exit 1
|
|
1305
1300
|
rescue Mysigner::ClientError => e
|
|
1306
1301
|
error "Activation failed: #{e.message}"
|
|
1307
|
-
say
|
|
1308
|
-
say
|
|
1309
|
-
say
|
|
1310
|
-
say
|
|
1311
|
-
say
|
|
1312
|
-
say
|
|
1302
|
+
say ''
|
|
1303
|
+
say '💡 Activation Failed: Try these steps', :cyan
|
|
1304
|
+
say ''
|
|
1305
|
+
say ' → Verify keystore ID is correct: mysigner keystore list', :yellow
|
|
1306
|
+
say ' → Check API token is valid: mysigner status', :yellow
|
|
1307
|
+
say ''
|
|
1313
1308
|
exit 1
|
|
1314
1309
|
end
|
|
1315
1310
|
|
|
@@ -1317,14 +1312,14 @@ module Mysigner
|
|
|
1317
1312
|
invoke :help, ['keystore']
|
|
1318
1313
|
else
|
|
1319
1314
|
error "Unknown action: #{action}"
|
|
1320
|
-
say
|
|
1315
|
+
say 'Available actions: list, upload, download, delete, activate, help', :yellow
|
|
1321
1316
|
exit 1
|
|
1322
1317
|
end
|
|
1323
1318
|
end
|
|
1324
1319
|
|
|
1325
1320
|
# ==================== ANDROID APP REGISTRATION ====================
|
|
1326
1321
|
|
|
1327
|
-
desc
|
|
1322
|
+
desc 'android SUBCOMMAND', 'Android commands (init, add, build, list)'
|
|
1328
1323
|
long_desc <<~DESC
|
|
1329
1324
|
Register and manage Android apps with My Signer.
|
|
1330
1325
|
|
|
@@ -1375,9 +1370,9 @@ module Mysigner
|
|
|
1375
1370
|
android_init(config, client)
|
|
1376
1371
|
when 'add'
|
|
1377
1372
|
if args.empty?
|
|
1378
|
-
error
|
|
1379
|
-
say
|
|
1380
|
-
say
|
|
1373
|
+
error 'Usage: mysigner android add PACKAGE_NAME [--name NAME]'
|
|
1374
|
+
say ''
|
|
1375
|
+
say 'Example: mysigner android add com.example.myapp --name "My App"', :yellow
|
|
1381
1376
|
exit 1
|
|
1382
1377
|
end
|
|
1383
1378
|
android_add(config, client, args[0], options[:name])
|
|
@@ -1390,7 +1385,7 @@ module Mysigner
|
|
|
1390
1385
|
invoke :help, ['android']
|
|
1391
1386
|
else
|
|
1392
1387
|
error "Unknown action: #{action}"
|
|
1393
|
-
say
|
|
1388
|
+
say 'Available actions: init, add, build, list, help', :yellow
|
|
1394
1389
|
exit 1
|
|
1395
1390
|
end
|
|
1396
1391
|
end
|
|
@@ -1400,8 +1395,8 @@ module Mysigner
|
|
|
1400
1395
|
def android_init(config, client)
|
|
1401
1396
|
require_relative '../build/android_parser'
|
|
1402
1397
|
|
|
1403
|
-
say
|
|
1404
|
-
say
|
|
1398
|
+
say '🔍 Detecting project...', :cyan
|
|
1399
|
+
say ''
|
|
1405
1400
|
|
|
1406
1401
|
package_name = nil
|
|
1407
1402
|
app_name = nil
|
|
@@ -1422,26 +1417,26 @@ module Mysigner
|
|
|
1422
1417
|
app_name = expo_config[:name]
|
|
1423
1418
|
project_type = 'Expo managed'
|
|
1424
1419
|
else
|
|
1425
|
-
error
|
|
1426
|
-
say
|
|
1420
|
+
error 'No Android project or Expo config found'
|
|
1421
|
+
say ''
|
|
1427
1422
|
say "For Expo managed projects, add 'android.package' to app.json:", :yellow
|
|
1428
|
-
say
|
|
1429
|
-
say
|
|
1430
|
-
say
|
|
1431
|
-
say
|
|
1432
|
-
say
|
|
1433
|
-
say
|
|
1434
|
-
say
|
|
1435
|
-
say
|
|
1423
|
+
say ''
|
|
1424
|
+
say ' {', :white
|
|
1425
|
+
say ' "expo": {', :white
|
|
1426
|
+
say ' "android": { "package": "com.yourcompany.app" }', :white
|
|
1427
|
+
say ' }', :white
|
|
1428
|
+
say ' }', :white
|
|
1429
|
+
say ''
|
|
1430
|
+
say 'Or run from a directory with an android/ folder (native/RN/Capacitor).', :yellow
|
|
1436
1431
|
exit 1
|
|
1437
1432
|
end
|
|
1438
1433
|
end
|
|
1439
1434
|
|
|
1440
1435
|
say "✓ Found: #{project_type} project", :green
|
|
1441
|
-
say
|
|
1436
|
+
say ''
|
|
1442
1437
|
say "📦 Package: #{package_name}", :cyan
|
|
1443
1438
|
say "📱 Name: #{app_name || '(not set)'}", :cyan
|
|
1444
|
-
say
|
|
1439
|
+
say ''
|
|
1445
1440
|
|
|
1446
1441
|
# Check if app already exists
|
|
1447
1442
|
begin
|
|
@@ -1453,21 +1448,21 @@ module Mysigner
|
|
|
1453
1448
|
existing = existing_apps.find { |a| a['package_name'] == package_name }
|
|
1454
1449
|
|
|
1455
1450
|
if existing
|
|
1456
|
-
say
|
|
1457
|
-
say
|
|
1458
|
-
say
|
|
1451
|
+
say 'ℹ️ App already registered!', :yellow
|
|
1452
|
+
say ''
|
|
1453
|
+
say 'Details:', :bold
|
|
1459
1454
|
say " ID: #{existing['id']}"
|
|
1460
1455
|
say " Name: #{existing['name'] || '(not set)'}"
|
|
1461
1456
|
say " Package: #{existing['package_name']}"
|
|
1462
1457
|
say " Builds: #{existing['builds_count'] || 0}"
|
|
1463
|
-
say
|
|
1464
|
-
say
|
|
1465
|
-
if (existing['builds_count'] || 0)
|
|
1466
|
-
say
|
|
1458
|
+
say ''
|
|
1459
|
+
say 'Next steps:', :cyan
|
|
1460
|
+
if (existing['builds_count'] || 0).positive?
|
|
1461
|
+
say ' • Ship to Play Store: mysigner ship internal --platform android', :white
|
|
1467
1462
|
else
|
|
1468
|
-
say
|
|
1469
|
-
say
|
|
1470
|
-
say
|
|
1463
|
+
say ' 1. Build AAB: mysigner android build', :white
|
|
1464
|
+
say ' 2. Upload first build manually in Play Console (one-time requirement)', :white
|
|
1465
|
+
say ' 3. After that: mysigner ship internal --platform android', :white
|
|
1471
1466
|
end
|
|
1472
1467
|
return
|
|
1473
1468
|
end
|
|
@@ -1476,7 +1471,7 @@ module Mysigner
|
|
|
1476
1471
|
end
|
|
1477
1472
|
|
|
1478
1473
|
# Register the app
|
|
1479
|
-
say
|
|
1474
|
+
say '🔗 Registering with My Signer...', :cyan
|
|
1480
1475
|
|
|
1481
1476
|
begin
|
|
1482
1477
|
response = client.post(
|
|
@@ -1488,21 +1483,20 @@ module Mysigner
|
|
|
1488
1483
|
)
|
|
1489
1484
|
|
|
1490
1485
|
app = response[:data]['android_app'] || response[:data]
|
|
1491
|
-
say
|
|
1492
|
-
say
|
|
1493
|
-
say
|
|
1486
|
+
say '✓ App registered successfully!', :green
|
|
1487
|
+
say ''
|
|
1488
|
+
say 'Details:', :bold
|
|
1494
1489
|
say " ID: #{app['id']}"
|
|
1495
1490
|
say " Name: #{app['name'] || '(not set)'}"
|
|
1496
1491
|
say " Package: #{app['package_name']}"
|
|
1497
|
-
say
|
|
1498
|
-
say
|
|
1499
|
-
say
|
|
1500
|
-
say
|
|
1501
|
-
say
|
|
1502
|
-
say
|
|
1503
|
-
|
|
1492
|
+
say ''
|
|
1493
|
+
say 'Next steps:', :cyan
|
|
1494
|
+
say ' 1. Create app in Google Play Console', :white
|
|
1495
|
+
say ' 2. Build AAB: mysigner android build', :white
|
|
1496
|
+
say ' 3. Upload first build manually in Play Console (one-time requirement)', :white
|
|
1497
|
+
say ' 4. After that: mysigner ship internal --platform android', :white
|
|
1504
1498
|
rescue Mysigner::ValidationError => e
|
|
1505
|
-
error
|
|
1499
|
+
error 'Validation failed:'
|
|
1506
1500
|
if e.details
|
|
1507
1501
|
e.details.each do |field, errors|
|
|
1508
1502
|
say " #{field}: #{errors.join(', ')}", :red
|
|
@@ -1519,11 +1513,11 @@ module Mysigner
|
|
|
1519
1513
|
end
|
|
1520
1514
|
|
|
1521
1515
|
def android_add(config, client, package_name, name = nil)
|
|
1522
|
-
say
|
|
1523
|
-
say
|
|
1516
|
+
say '📦 Registering Android app...', :cyan
|
|
1517
|
+
say ''
|
|
1524
1518
|
say " Package: #{package_name}", :white
|
|
1525
1519
|
say " Name: #{name || '(will be synced from Play Store)'}", :white
|
|
1526
|
-
say
|
|
1520
|
+
say ''
|
|
1527
1521
|
|
|
1528
1522
|
begin
|
|
1529
1523
|
response = client.post(
|
|
@@ -1535,25 +1529,24 @@ module Mysigner
|
|
|
1535
1529
|
)
|
|
1536
1530
|
|
|
1537
1531
|
app = response[:data]['android_app'] || response[:data]
|
|
1538
|
-
say
|
|
1539
|
-
say
|
|
1540
|
-
say
|
|
1532
|
+
say '✓ App registered successfully!', :green
|
|
1533
|
+
say ''
|
|
1534
|
+
say 'Details:', :bold
|
|
1541
1535
|
say " ID: #{app['id']}"
|
|
1542
1536
|
say " Name: #{app['name'] || '(not set)'}"
|
|
1543
1537
|
say " Package: #{app['package_name']}"
|
|
1544
|
-
say
|
|
1545
|
-
say
|
|
1546
|
-
if (app['builds_count'] || 0)
|
|
1547
|
-
say
|
|
1538
|
+
say ''
|
|
1539
|
+
say 'Next steps:', :cyan
|
|
1540
|
+
if (app['builds_count'] || 0).positive?
|
|
1541
|
+
say ' • Ship to Play Store: mysigner ship internal --platform android', :white
|
|
1548
1542
|
else
|
|
1549
|
-
say
|
|
1550
|
-
say
|
|
1551
|
-
say
|
|
1552
|
-
say
|
|
1543
|
+
say ' 1. Create app in Google Play Console (if not done)', :white
|
|
1544
|
+
say ' 2. Build AAB: mysigner android build', :white
|
|
1545
|
+
say ' 3. Upload first build manually in Play Console', :white
|
|
1546
|
+
say ' 4. Then use: mysigner ship internal --platform android', :white
|
|
1553
1547
|
end
|
|
1554
|
-
|
|
1555
1548
|
rescue Mysigner::ValidationError => e
|
|
1556
|
-
error
|
|
1549
|
+
error 'Validation failed:'
|
|
1557
1550
|
if e.details
|
|
1558
1551
|
e.details.each do |field, errors|
|
|
1559
1552
|
say " #{field}: #{errors.join(', ')}", :red
|
|
@@ -1564,10 +1557,10 @@ module Mysigner
|
|
|
1564
1557
|
say " Suggestion: #{e.suggestion}", :yellow if e.suggestion
|
|
1565
1558
|
exit 1
|
|
1566
1559
|
rescue Mysigner::ClientError => e
|
|
1567
|
-
if e.message.include?(
|
|
1568
|
-
error
|
|
1569
|
-
say
|
|
1570
|
-
say
|
|
1560
|
+
if e.message.include?('already exists') || e.message.include?('taken')
|
|
1561
|
+
error 'An app with this package name already exists'
|
|
1562
|
+
say ''
|
|
1563
|
+
say 'List your apps with: mysigner android list', :yellow
|
|
1571
1564
|
else
|
|
1572
1565
|
error "Failed to register app: #{e.message}"
|
|
1573
1566
|
end
|
|
@@ -1576,32 +1569,32 @@ module Mysigner
|
|
|
1576
1569
|
end
|
|
1577
1570
|
|
|
1578
1571
|
def android_build
|
|
1579
|
-
say
|
|
1580
|
-
say
|
|
1572
|
+
say '🔨 Building Android App Bundle (AAB)...', :cyan
|
|
1573
|
+
say ''
|
|
1581
1574
|
|
|
1582
1575
|
begin
|
|
1583
1576
|
require_relative '../build/android_parser'
|
|
1584
1577
|
require_relative '../signing/keystore_manager'
|
|
1585
|
-
|
|
1578
|
+
|
|
1586
1579
|
project_dir = Dir.pwd
|
|
1587
1580
|
is_expo = expo_project?(project_dir)
|
|
1588
|
-
|
|
1581
|
+
|
|
1589
1582
|
# For Expo, we may need to regenerate android folder with correct versionCode
|
|
1590
1583
|
# Get package name from app.json first if Expo
|
|
1591
1584
|
if is_expo
|
|
1592
1585
|
expo_config = parse_expo_config(project_dir)
|
|
1593
1586
|
package_name = expo_config[:package_name]
|
|
1594
1587
|
local_version_code = expo_config[:version_code] || 1
|
|
1595
|
-
version_name = expo_config[:version] ||
|
|
1596
|
-
|
|
1588
|
+
version_name = expo_config[:version] || '1.0.0'
|
|
1589
|
+
|
|
1597
1590
|
# Check highest version code from API
|
|
1598
1591
|
highest_version_code = fetch_highest_version_code(package_name)
|
|
1599
1592
|
version_code = local_version_code
|
|
1600
1593
|
needs_increment = highest_version_code && local_version_code <= highest_version_code
|
|
1601
|
-
|
|
1594
|
+
|
|
1602
1595
|
if needs_increment
|
|
1603
1596
|
version_code = highest_version_code + 1
|
|
1604
|
-
|
|
1597
|
+
|
|
1605
1598
|
# Check if android folder already has the correct versionCode
|
|
1606
1599
|
android_dir = File.join(project_dir, 'android')
|
|
1607
1600
|
current_android_version = nil
|
|
@@ -1609,49 +1602,54 @@ module Mysigner
|
|
|
1609
1602
|
build_gradle = File.join(android_dir, 'app', 'build.gradle')
|
|
1610
1603
|
if File.exist?(build_gradle)
|
|
1611
1604
|
content = File.read(build_gradle)
|
|
1612
|
-
if content =~ /versionCode\s+(\d+)/
|
|
1613
|
-
current_android_version = $1.to_i
|
|
1614
|
-
end
|
|
1605
|
+
current_android_version = ::Regexp.last_match(1).to_i if content =~ /versionCode\s+(\d+)/
|
|
1615
1606
|
end
|
|
1616
1607
|
end
|
|
1617
|
-
|
|
1618
|
-
say
|
|
1608
|
+
|
|
1609
|
+
say '🔧 Framework: Expo (React Native)', :white
|
|
1619
1610
|
say "📦 Package: #{package_name}", :white
|
|
1620
|
-
|
|
1611
|
+
|
|
1621
1612
|
if current_android_version == version_code
|
|
1622
1613
|
# Android folder already has correct version, no need to regenerate
|
|
1623
1614
|
say "🔢 Version: #{version_name} (#{version_code})", :white
|
|
1624
|
-
say
|
|
1615
|
+
say ' ↳ Already at correct version code', :green
|
|
1625
1616
|
else
|
|
1626
1617
|
say "🔢 Version: #{version_name} (#{local_version_code} → #{version_code})", :white
|
|
1627
1618
|
say " ↳ Auto-incremented (#{highest_version_code} already on Play Store)", :yellow
|
|
1628
|
-
say
|
|
1629
|
-
|
|
1619
|
+
say ''
|
|
1620
|
+
|
|
1630
1621
|
# Regenerate android folder with new versionCode
|
|
1631
1622
|
say "🔄 Regenerating android folder with version code #{version_code}...", :yellow
|
|
1632
1623
|
regenerate_expo_android(project_dir, version_code)
|
|
1633
1624
|
end
|
|
1634
1625
|
end
|
|
1635
1626
|
end
|
|
1636
|
-
|
|
1627
|
+
|
|
1637
1628
|
# Now detect project (android folder should exist)
|
|
1638
1629
|
project_info = Build::Detector.detect_android(project_dir)
|
|
1639
1630
|
framework = project_info[:framework]
|
|
1640
1631
|
parser = Build::AndroidParser.new(project_info)
|
|
1641
|
-
|
|
1632
|
+
|
|
1642
1633
|
package_name ||= parser.application_id
|
|
1643
1634
|
version_name ||= parser.version_name
|
|
1644
1635
|
local_version_code ||= parser.version_code.to_i
|
|
1645
1636
|
android_dir = project_info[:android_directory] || File.join(project_info[:directory], 'android')
|
|
1646
1637
|
|
|
1647
1638
|
# For non-Expo, check version code now
|
|
1648
|
-
|
|
1639
|
+
if is_expo
|
|
1640
|
+
# Expo - already printed above, just show if no increment was needed
|
|
1641
|
+
unless needs_increment
|
|
1642
|
+
say '🔧 Framework: Expo (React Native)', :white
|
|
1643
|
+
say "📦 Package: #{package_name}", :white
|
|
1644
|
+
say "🔢 Version: #{version_name} (#{version_code})", :white
|
|
1645
|
+
end
|
|
1646
|
+
else
|
|
1649
1647
|
say "🔧 Framework: #{framework.to_s.gsub('_', ' ').capitalize}", :white
|
|
1650
1648
|
say "📦 Package: #{package_name}", :white
|
|
1651
|
-
|
|
1649
|
+
|
|
1652
1650
|
highest_version_code = fetch_highest_version_code(package_name)
|
|
1653
1651
|
version_code = local_version_code
|
|
1654
|
-
|
|
1652
|
+
|
|
1655
1653
|
if highest_version_code && local_version_code <= highest_version_code
|
|
1656
1654
|
version_code = highest_version_code + 1
|
|
1657
1655
|
say "🔢 Version: #{version_name} (#{local_version_code} → #{version_code})", :white
|
|
@@ -1659,84 +1657,78 @@ module Mysigner
|
|
|
1659
1657
|
else
|
|
1660
1658
|
say "🔢 Version: #{version_name} (#{version_code})", :white
|
|
1661
1659
|
end
|
|
1662
|
-
else
|
|
1663
|
-
# Expo - already printed above, just show if no increment was needed
|
|
1664
|
-
unless needs_increment
|
|
1665
|
-
say "🔧 Framework: Expo (React Native)", :white
|
|
1666
|
-
say "📦 Package: #{package_name}", :white
|
|
1667
|
-
say "🔢 Version: #{version_name} (#{version_code})", :white
|
|
1668
|
-
end
|
|
1669
1660
|
end
|
|
1670
|
-
say
|
|
1661
|
+
say ''
|
|
1671
1662
|
|
|
1672
1663
|
# Try to get keystore from MySigner
|
|
1673
1664
|
keystore_info = fetch_keystore_for_build(package_name)
|
|
1674
1665
|
if keystore_info
|
|
1675
1666
|
say "🔐 Keystore: #{keystore_info[:name]}", :green
|
|
1676
1667
|
else
|
|
1677
|
-
say
|
|
1668
|
+
say '⚠️ No keystore configured - will use debug signing', :yellow
|
|
1678
1669
|
say " Run 'mysigner android init' to set up release signing", :yellow
|
|
1679
1670
|
end
|
|
1680
|
-
say
|
|
1681
|
-
say
|
|
1682
|
-
say
|
|
1671
|
+
say ''
|
|
1672
|
+
say '⏱️ This may take a few minutes...', :yellow
|
|
1673
|
+
say ''
|
|
1683
1674
|
|
|
1684
1675
|
# Build based on framework (pass version_code override if incremented)
|
|
1685
1676
|
# For Expo, we already regenerated with correct version, so no override needed
|
|
1686
1677
|
version_code_override = nil
|
|
1687
1678
|
unless is_expo
|
|
1688
|
-
version_code_override =
|
|
1679
|
+
version_code_override = version_code == local_version_code ? nil : version_code
|
|
1689
1680
|
end
|
|
1690
|
-
|
|
1681
|
+
|
|
1691
1682
|
aab_path = case framework
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1683
|
+
when :flutter
|
|
1684
|
+
build_flutter_aab(project_dir, keystore_info, version_code_override)
|
|
1685
|
+
when :maui, :xamarin, :xamarin_forms
|
|
1686
|
+
build_dotnet_aab(project_dir, project_info[:csproj_path], framework, keystore_info,
|
|
1687
|
+
version_code_override)
|
|
1688
|
+
when :react_native, :capacitor, :native
|
|
1689
|
+
build_gradle_aab(android_dir, framework, keystore_info, version_code_override)
|
|
1690
|
+
else
|
|
1691
|
+
build_gradle_aab(android_dir, framework, keystore_info, version_code_override)
|
|
1692
|
+
end
|
|
1701
1693
|
|
|
1702
1694
|
unless aab_path && File.exist?(aab_path)
|
|
1703
|
-
error
|
|
1704
|
-
say
|
|
1695
|
+
error 'AAB file not found after build'
|
|
1696
|
+
say 'Check build output for errors.', :yellow
|
|
1705
1697
|
exit 1
|
|
1706
1698
|
end
|
|
1707
1699
|
|
|
1708
|
-
say
|
|
1709
|
-
say
|
|
1710
|
-
say
|
|
1711
|
-
say
|
|
1712
|
-
say
|
|
1700
|
+
say ''
|
|
1701
|
+
say '=' * 80, :green
|
|
1702
|
+
say '✓ Build complete!', :green
|
|
1703
|
+
say '=' * 80, :green
|
|
1704
|
+
say ''
|
|
1713
1705
|
say "📦 AAB: #{aab_path}", :cyan
|
|
1714
1706
|
say "📊 Size: #{format_bytes(File.size(aab_path))}", :cyan
|
|
1715
|
-
say
|
|
1716
|
-
say
|
|
1717
|
-
say
|
|
1718
|
-
say
|
|
1719
|
-
say
|
|
1720
|
-
say
|
|
1721
|
-
say
|
|
1722
|
-
|
|
1707
|
+
say ''
|
|
1708
|
+
say 'Next step:', :bold
|
|
1709
|
+
say ' Upload this AAB to Google Play Console → Internal testing → Create release', :white
|
|
1710
|
+
say ''
|
|
1711
|
+
say 'After uploading, you can use:', :cyan
|
|
1712
|
+
say ' mysigner ship internal --platform android', :green
|
|
1713
|
+
say ''
|
|
1714
|
+
|
|
1723
1715
|
# Open the folder containing the AAB
|
|
1724
1716
|
aab_dir = File.dirname(aab_path)
|
|
1725
|
-
say
|
|
1726
|
-
|
|
1717
|
+
say '📂 Opening folder...', :yellow
|
|
1718
|
+
case RUBY_PLATFORM
|
|
1719
|
+
when /darwin/
|
|
1727
1720
|
system('open', aab_dir)
|
|
1728
|
-
|
|
1721
|
+
when /linux/
|
|
1729
1722
|
system('xdg-open', aab_dir)
|
|
1730
|
-
|
|
1723
|
+
when /mingw|mswin/
|
|
1731
1724
|
system('explorer', aab_dir.gsub('/', '\\'))
|
|
1732
1725
|
end
|
|
1733
|
-
|
|
1734
1726
|
rescue Build::Detector::NoProjectError => e
|
|
1735
1727
|
error e.message
|
|
1736
|
-
say
|
|
1737
|
-
say
|
|
1728
|
+
say ''
|
|
1729
|
+
say 'Run this command from an Android project directory.', :yellow
|
|
1738
1730
|
exit 1
|
|
1739
|
-
rescue => e
|
|
1731
|
+
rescue StandardError => e
|
|
1740
1732
|
error "Build failed: #{e.message}"
|
|
1741
1733
|
exit 1
|
|
1742
1734
|
end
|
|
@@ -1745,19 +1737,17 @@ module Mysigner
|
|
|
1745
1737
|
def build_flutter_aab(project_dir, keystore_info = nil, version_code_override = nil)
|
|
1746
1738
|
# Check for flutter
|
|
1747
1739
|
unless system('which flutter > /dev/null 2>&1')
|
|
1748
|
-
error
|
|
1749
|
-
say
|
|
1740
|
+
error 'Flutter not found in PATH'
|
|
1741
|
+
say 'Install Flutter: https://flutter.dev/docs/get-started/install', :yellow
|
|
1750
1742
|
exit 1
|
|
1751
1743
|
end
|
|
1752
1744
|
|
|
1753
1745
|
Dir.chdir(project_dir) do
|
|
1754
1746
|
args = ['flutter', 'build', 'appbundle', '--release']
|
|
1755
|
-
|
|
1747
|
+
|
|
1756
1748
|
# Add version code override
|
|
1757
|
-
if version_code_override
|
|
1758
|
-
|
|
1759
|
-
end
|
|
1760
|
-
|
|
1749
|
+
args += ['--build-number', version_code_override.to_s] if version_code_override
|
|
1750
|
+
|
|
1761
1751
|
# Add signing if keystore provided (Flutter reads key.properties from android/)
|
|
1762
1752
|
if keystore_info
|
|
1763
1753
|
# Create key.properties for Flutter
|
|
@@ -1769,10 +1759,10 @@ module Mysigner
|
|
|
1769
1759
|
storeFile=#{keystore_info[:path]}
|
|
1770
1760
|
PROPS
|
|
1771
1761
|
end
|
|
1772
|
-
|
|
1762
|
+
|
|
1773
1763
|
success = system(*args)
|
|
1774
1764
|
unless success
|
|
1775
|
-
error
|
|
1765
|
+
error 'Flutter build failed'
|
|
1776
1766
|
exit 1
|
|
1777
1767
|
end
|
|
1778
1768
|
end
|
|
@@ -1798,46 +1788,41 @@ module Mysigner
|
|
|
1798
1788
|
app_json_path = File.join(project_dir, 'app.json')
|
|
1799
1789
|
android_dir = File.join(project_dir, 'android')
|
|
1800
1790
|
local_props_path = File.join(android_dir, 'local.properties')
|
|
1801
|
-
|
|
1791
|
+
|
|
1802
1792
|
# Preserve local.properties if it exists
|
|
1803
1793
|
local_props_content = File.read(local_props_path) if File.exist?(local_props_path)
|
|
1804
|
-
|
|
1794
|
+
|
|
1805
1795
|
# Read original app.json
|
|
1806
1796
|
original_content = File.read(app_json_path)
|
|
1807
1797
|
config = JSON.parse(original_content)
|
|
1808
|
-
|
|
1798
|
+
|
|
1809
1799
|
# Set the new versionCode
|
|
1810
1800
|
config['expo'] ||= {}
|
|
1811
1801
|
config['expo']['android'] ||= {}
|
|
1812
1802
|
config['expo']['android']['versionCode'] = new_version_code
|
|
1813
|
-
|
|
1803
|
+
|
|
1814
1804
|
# Write modified app.json
|
|
1815
1805
|
File.write(app_json_path, JSON.pretty_generate(config))
|
|
1816
|
-
|
|
1806
|
+
|
|
1817
1807
|
begin
|
|
1818
1808
|
# Delete existing android folder
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1809
|
+
|
|
1810
|
+
FileUtils.rm_rf(android_dir)
|
|
1811
|
+
|
|
1823
1812
|
# Run expo prebuild
|
|
1824
1813
|
Dir.chdir(project_dir) do
|
|
1825
1814
|
success = system('npx', 'expo', 'prebuild', '--platform', 'android', '--clean')
|
|
1826
|
-
unless success
|
|
1827
|
-
raise "expo prebuild failed"
|
|
1828
|
-
end
|
|
1815
|
+
raise 'expo prebuild failed' unless success
|
|
1829
1816
|
end
|
|
1830
|
-
|
|
1817
|
+
|
|
1831
1818
|
# Restore local.properties if we had one, or create default
|
|
1832
1819
|
if local_props_content
|
|
1833
1820
|
File.write(local_props_path, local_props_content)
|
|
1834
1821
|
else
|
|
1835
1822
|
# Try to detect Android SDK and create local.properties
|
|
1836
|
-
sdk_path = ENV['ANDROID_HOME'] || ENV['ANDROID_SDK_ROOT'] ||
|
|
1823
|
+
sdk_path = ENV['ANDROID_HOME'] || ENV['ANDROID_SDK_ROOT'] ||
|
|
1837
1824
|
File.expand_path('~/Library/Android/sdk')
|
|
1838
|
-
if Dir.exist?(sdk_path)
|
|
1839
|
-
File.write(local_props_path, "sdk.dir=#{sdk_path}\n")
|
|
1840
|
-
end
|
|
1825
|
+
File.write(local_props_path, "sdk.dir=#{sdk_path}\n") if Dir.exist?(sdk_path)
|
|
1841
1826
|
end
|
|
1842
1827
|
ensure
|
|
1843
1828
|
# Restore original app.json
|
|
@@ -1848,19 +1833,22 @@ module Mysigner
|
|
|
1848
1833
|
def fetch_highest_version_code(package_name)
|
|
1849
1834
|
config = Mysigner::Config.new
|
|
1850
1835
|
return nil unless config.exists?
|
|
1836
|
+
|
|
1851
1837
|
config.load
|
|
1852
1838
|
return nil unless config.api_token && config.organization_id
|
|
1853
1839
|
|
|
1854
|
-
client = Mysigner::Client.new(api_url: config.api_url, api_token: config.api_token,
|
|
1840
|
+
client = Mysigner::Client.new(api_url: config.api_url, api_token: config.api_token,
|
|
1841
|
+
user_email: config.user_email)
|
|
1855
1842
|
|
|
1856
1843
|
# Find app by package name
|
|
1857
1844
|
response = client.get("/api/v1/organizations/#{config.organization_id}/android_apps")
|
|
1858
1845
|
apps = response[:data]['android_apps'] || []
|
|
1859
1846
|
app = apps.find { |a| a['package_name'] == package_name }
|
|
1860
|
-
|
|
1847
|
+
|
|
1861
1848
|
return app['highest_version_code'].to_i if app && app['highest_version_code']
|
|
1849
|
+
|
|
1862
1850
|
nil
|
|
1863
|
-
rescue
|
|
1851
|
+
rescue StandardError
|
|
1864
1852
|
# Silently fail - we'll use local version
|
|
1865
1853
|
nil
|
|
1866
1854
|
end
|
|
@@ -1868,10 +1856,12 @@ module Mysigner
|
|
|
1868
1856
|
def fetch_keystore_for_build(package_name)
|
|
1869
1857
|
config = Mysigner::Config.new
|
|
1870
1858
|
return nil unless config.exists?
|
|
1859
|
+
|
|
1871
1860
|
config.load
|
|
1872
1861
|
return nil unless config.api_token && config.organization_id
|
|
1873
1862
|
|
|
1874
|
-
client = Mysigner::Client.new(api_url: config.api_url, api_token: config.api_token,
|
|
1863
|
+
client = Mysigner::Client.new(api_url: config.api_url, api_token: config.api_token,
|
|
1864
|
+
user_email: config.user_email)
|
|
1875
1865
|
keystore_manager = Signing::KeystoreManager.new(client, config.organization_id)
|
|
1876
1866
|
|
|
1877
1867
|
# Find app by package name to get its keystore
|
|
@@ -1909,59 +1899,57 @@ module Mysigner
|
|
|
1909
1899
|
end
|
|
1910
1900
|
|
|
1911
1901
|
nil
|
|
1912
|
-
rescue
|
|
1902
|
+
rescue StandardError
|
|
1913
1903
|
# Silently fail - we'll use debug signing
|
|
1914
1904
|
nil
|
|
1915
1905
|
end
|
|
1916
1906
|
|
|
1917
|
-
def build_dotnet_aab(project_dir,
|
|
1907
|
+
def build_dotnet_aab(project_dir, _csproj_path, framework, keystore_info = nil, version_code_override = nil)
|
|
1918
1908
|
# Check for dotnet
|
|
1919
1909
|
unless system('which dotnet > /dev/null 2>&1')
|
|
1920
|
-
error
|
|
1921
|
-
say
|
|
1910
|
+
error '.NET SDK not found in PATH'
|
|
1911
|
+
say 'Install .NET: https://dotnet.microsoft.com/download', :yellow
|
|
1922
1912
|
exit 1
|
|
1923
1913
|
end
|
|
1924
1914
|
|
|
1925
1915
|
Dir.chdir(project_dir) do
|
|
1926
1916
|
base_args = []
|
|
1927
|
-
|
|
1917
|
+
|
|
1928
1918
|
# Add signing args if keystore provided
|
|
1929
1919
|
if keystore_info
|
|
1930
1920
|
base_args += [
|
|
1931
|
-
|
|
1921
|
+
'-p:AndroidKeyStore=true',
|
|
1932
1922
|
"-p:AndroidSigningKeyStore=#{keystore_info[:path]}",
|
|
1933
1923
|
"-p:AndroidSigningKeyAlias=#{keystore_info[:key_alias]}",
|
|
1934
1924
|
"-p:AndroidSigningKeyPass=#{keystore_info[:key_password]}",
|
|
1935
1925
|
"-p:AndroidSigningStorePass=#{keystore_info[:password]}"
|
|
1936
1926
|
]
|
|
1937
1927
|
end
|
|
1938
|
-
|
|
1928
|
+
|
|
1939
1929
|
# Add version code override
|
|
1940
|
-
if version_code_override
|
|
1941
|
-
base_args << "-p:ApplicationVersion=#{version_code_override}"
|
|
1942
|
-
end
|
|
1930
|
+
base_args << "-p:ApplicationVersion=#{version_code_override}" if version_code_override
|
|
1943
1931
|
|
|
1944
1932
|
# MAUI uses dotnet publish with Android target
|
|
1945
|
-
if framework == :maui
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1933
|
+
success = if framework == :maui
|
|
1934
|
+
system(
|
|
1935
|
+
'dotnet', 'publish',
|
|
1936
|
+
'-f', 'net8.0-android',
|
|
1937
|
+
'-c', 'Release',
|
|
1938
|
+
'-p:AndroidPackageFormat=aab',
|
|
1939
|
+
*base_args
|
|
1940
|
+
)
|
|
1941
|
+
else
|
|
1942
|
+
# Xamarin uses msbuild
|
|
1943
|
+
system(
|
|
1944
|
+
'dotnet', 'build',
|
|
1945
|
+
'-c', 'Release',
|
|
1946
|
+
'-p:AndroidPackageFormat=aab',
|
|
1947
|
+
*base_args
|
|
1948
|
+
)
|
|
1949
|
+
end
|
|
1962
1950
|
|
|
1963
1951
|
unless success
|
|
1964
|
-
error
|
|
1952
|
+
error '.NET build failed'
|
|
1965
1953
|
exit 1
|
|
1966
1954
|
end
|
|
1967
1955
|
end
|
|
@@ -1983,14 +1971,14 @@ module Mysigner
|
|
|
1983
1971
|
when :capacitor
|
|
1984
1972
|
say "Run 'npx cap sync android' first.", :yellow
|
|
1985
1973
|
else
|
|
1986
|
-
say
|
|
1974
|
+
say 'Ensure the android/ folder has gradlew.', :yellow
|
|
1987
1975
|
end
|
|
1988
1976
|
exit 1
|
|
1989
1977
|
end
|
|
1990
1978
|
|
|
1991
1979
|
# Build gradle command with signing via command-line properties
|
|
1992
1980
|
gradle_args = ['./gradlew', 'bundleRelease', '--warning-mode=all']
|
|
1993
|
-
|
|
1981
|
+
|
|
1994
1982
|
if keystore_info
|
|
1995
1983
|
# Pass signing config via command-line properties (no file modification needed)
|
|
1996
1984
|
gradle_args += [
|
|
@@ -2000,16 +1988,14 @@ module Mysigner
|
|
|
2000
1988
|
"-Pandroid.injected.signing.key.password=#{keystore_info[:key_password]}"
|
|
2001
1989
|
]
|
|
2002
1990
|
end
|
|
2003
|
-
|
|
1991
|
+
|
|
2004
1992
|
# Pass version code override if provided (no file modification needed)
|
|
2005
|
-
if version_code_override
|
|
2006
|
-
gradle_args << "-PversionCode=#{version_code_override}"
|
|
2007
|
-
end
|
|
1993
|
+
gradle_args << "-PversionCode=#{version_code_override}" if version_code_override
|
|
2008
1994
|
|
|
2009
1995
|
Dir.chdir(android_dir) do
|
|
2010
1996
|
success = system(*gradle_args)
|
|
2011
1997
|
unless success
|
|
2012
|
-
error
|
|
1998
|
+
error 'Gradle build failed'
|
|
2013
1999
|
exit 1
|
|
2014
2000
|
end
|
|
2015
2001
|
end
|
|
@@ -2049,11 +2035,9 @@ module Mysigner
|
|
|
2049
2035
|
content = File.read(app_config_path)
|
|
2050
2036
|
# Basic regex extraction for package name
|
|
2051
2037
|
if content =~ /android\s*:\s*\{[^}]*package\s*:\s*["']([^"']+)["']/m
|
|
2052
|
-
package_name =
|
|
2038
|
+
package_name = ::Regexp.last_match(1)
|
|
2053
2039
|
name = nil
|
|
2054
|
-
if content =~ /name\s*:\s*["']([^"']+)["']/
|
|
2055
|
-
name = $1
|
|
2056
|
-
end
|
|
2040
|
+
name = ::Regexp.last_match(1) if content =~ /name\s*:\s*["']([^"']+)["']/
|
|
2057
2041
|
return {
|
|
2058
2042
|
package_name: package_name,
|
|
2059
2043
|
bundle_id: nil,
|
|
@@ -2069,7 +2053,7 @@ module Mysigner
|
|
|
2069
2053
|
|
|
2070
2054
|
# ==================== BUNDLE IDS ====================
|
|
2071
2055
|
|
|
2072
|
-
desc
|
|
2056
|
+
desc 'bundleid SUBCOMMAND', 'Register and manage iOS Bundle IDs'
|
|
2073
2057
|
long_desc <<~DESC
|
|
2074
2058
|
Register and manage iOS Bundle IDs in App Store Connect.
|
|
2075
2059
|
|
|
@@ -2116,10 +2100,10 @@ module Mysigner
|
|
|
2116
2100
|
case action
|
|
2117
2101
|
when 'register'
|
|
2118
2102
|
if args.empty?
|
|
2119
|
-
error
|
|
2120
|
-
say
|
|
2121
|
-
say
|
|
2122
|
-
say
|
|
2103
|
+
error 'Usage: mysigner bundleid register IDENTIFIER [NAME]'
|
|
2104
|
+
say ''
|
|
2105
|
+
say 'Example: mysigner bundleid register com.company.myapp', :yellow
|
|
2106
|
+
say 'Example: mysigner bundleid register com.company.myapp.widget "My Widget"', :yellow
|
|
2123
2107
|
exit 1
|
|
2124
2108
|
end
|
|
2125
2109
|
|
|
@@ -2130,19 +2114,19 @@ module Mysigner
|
|
|
2130
2114
|
# Validate bundle ID format
|
|
2131
2115
|
unless identifier =~ /^[a-zA-Z][a-zA-Z0-9.-]*\.[a-zA-Z][a-zA-Z0-9.-]*$/
|
|
2132
2116
|
error "Invalid Bundle ID format: #{identifier}"
|
|
2133
|
-
say
|
|
2134
|
-
say
|
|
2135
|
-
say
|
|
2136
|
-
say
|
|
2137
|
-
say
|
|
2117
|
+
say ''
|
|
2118
|
+
say 'Bundle IDs must:', :yellow
|
|
2119
|
+
say ' • Start with a letter', :cyan
|
|
2120
|
+
say ' • Use reverse domain notation (e.g., com.company.app)', :cyan
|
|
2121
|
+
say ' • Contain only letters, numbers, hyphens, and periods', :cyan
|
|
2138
2122
|
exit 1
|
|
2139
2123
|
end
|
|
2140
2124
|
|
|
2141
|
-
say
|
|
2142
|
-
say
|
|
2125
|
+
say '🔗 Registering Bundle ID...', :cyan
|
|
2126
|
+
say ''
|
|
2143
2127
|
say " Identifier: #{identifier}", :white
|
|
2144
2128
|
say " Name: #{name}", :white
|
|
2145
|
-
say
|
|
2129
|
+
say ''
|
|
2146
2130
|
|
|
2147
2131
|
begin
|
|
2148
2132
|
response = client.post(
|
|
@@ -2155,18 +2139,18 @@ module Mysigner
|
|
|
2155
2139
|
)
|
|
2156
2140
|
|
|
2157
2141
|
bundle_id_data = response[:data]['bundle_id'] || response[:data]
|
|
2158
|
-
say
|
|
2159
|
-
say
|
|
2160
|
-
say
|
|
2142
|
+
say '✓ Bundle ID registered successfully!', :green
|
|
2143
|
+
say ''
|
|
2144
|
+
say 'Details:', :bold
|
|
2161
2145
|
say " Identifier: #{bundle_id_data['identifier'] || identifier}"
|
|
2162
2146
|
say " Name: #{bundle_id_data['name'] || name}"
|
|
2163
|
-
say
|
|
2164
|
-
say
|
|
2165
|
-
say
|
|
2166
|
-
say
|
|
2167
|
-
say
|
|
2147
|
+
say ''
|
|
2148
|
+
say 'Next steps:', :cyan
|
|
2149
|
+
say ' 1. Sync to update local cache: mysigner sync ios', :white
|
|
2150
|
+
say ' 2. Create a provisioning profile: mysigner doctor (will auto-create)', :white
|
|
2151
|
+
say ' 3. Or run: mysigner signing configure', :white
|
|
2168
2152
|
rescue Mysigner::ValidationError => e
|
|
2169
|
-
error
|
|
2153
|
+
error 'Validation failed:'
|
|
2170
2154
|
if e.details
|
|
2171
2155
|
e.details.each do |field, errors|
|
|
2172
2156
|
say " #{field}: #{errors.join(', ')}", :red
|
|
@@ -2177,25 +2161,25 @@ module Mysigner
|
|
|
2177
2161
|
say " Suggestion: #{e.suggestion}", :yellow if e.suggestion
|
|
2178
2162
|
exit 1
|
|
2179
2163
|
rescue Mysigner::ClientError => e
|
|
2180
|
-
if e.message.include?(
|
|
2164
|
+
if e.message.include?('already exists') || e.message.include?('ENTITY_ERROR.ATTRIBUTE.INVALID.DUPLICATE')
|
|
2181
2165
|
say "ℹ️ Bundle ID already registered: #{identifier}", :yellow
|
|
2182
|
-
say
|
|
2183
|
-
say
|
|
2166
|
+
say ''
|
|
2167
|
+
say 'This Bundle ID already exists in App Store Connect.', :white
|
|
2184
2168
|
say "Run 'mysigner sync ios' to update your local cache.", :cyan
|
|
2185
2169
|
else
|
|
2186
2170
|
error "Failed to register Bundle ID: #{e.message}"
|
|
2187
|
-
say
|
|
2188
|
-
say
|
|
2189
|
-
say
|
|
2190
|
-
say
|
|
2191
|
-
say
|
|
2171
|
+
say ''
|
|
2172
|
+
say 'Common issues:', :yellow
|
|
2173
|
+
say ' • Bundle ID already exists (check App Store Connect)', :cyan
|
|
2174
|
+
say ' • Invalid format (must be like com.company.app)', :cyan
|
|
2175
|
+
say ' • API credentials may not have permission', :cyan
|
|
2192
2176
|
end
|
|
2193
2177
|
exit 1
|
|
2194
2178
|
end
|
|
2195
2179
|
|
|
2196
2180
|
when 'list'
|
|
2197
|
-
say
|
|
2198
|
-
say
|
|
2181
|
+
say '📦 Registered Bundle IDs', :cyan
|
|
2182
|
+
say ''
|
|
2199
2183
|
|
|
2200
2184
|
begin
|
|
2201
2185
|
response = client.get(
|
|
@@ -2204,16 +2188,16 @@ module Mysigner
|
|
|
2204
2188
|
bundle_ids = response[:data]['bundle_ids'] || response[:data] || []
|
|
2205
2189
|
|
|
2206
2190
|
if bundle_ids.empty?
|
|
2207
|
-
say
|
|
2208
|
-
say
|
|
2209
|
-
say
|
|
2191
|
+
say ' No Bundle IDs found', :yellow
|
|
2192
|
+
say ''
|
|
2193
|
+
say ' Register one with: mysigner bundleid register com.company.app', :cyan
|
|
2210
2194
|
else
|
|
2211
2195
|
bundle_ids.each do |bid|
|
|
2212
2196
|
identifier = bid['identifier'] || bid['bundle_id']
|
|
2213
2197
|
name = bid['name'] || 'N/A'
|
|
2214
2198
|
say " • #{name}", :green
|
|
2215
2199
|
say " Identifier: #{identifier}"
|
|
2216
|
-
say
|
|
2200
|
+
say ''
|
|
2217
2201
|
end
|
|
2218
2202
|
end
|
|
2219
2203
|
rescue Mysigner::ClientError => e
|
|
@@ -2223,17 +2207,17 @@ module Mysigner
|
|
|
2223
2207
|
|
|
2224
2208
|
else
|
|
2225
2209
|
error "Unknown action: #{action}"
|
|
2226
|
-
say
|
|
2227
|
-
say
|
|
2228
|
-
say
|
|
2229
|
-
say
|
|
2210
|
+
say ''
|
|
2211
|
+
say 'Available actions:', :yellow
|
|
2212
|
+
say ' mysigner bundleid register IDENTIFIER [NAME]', :cyan
|
|
2213
|
+
say ' mysigner bundleid list', :cyan
|
|
2230
2214
|
exit 1
|
|
2231
2215
|
end
|
|
2232
2216
|
end
|
|
2233
2217
|
|
|
2234
2218
|
# ==================== APPS (iOS + Android) ====================
|
|
2235
2219
|
|
|
2236
|
-
desc
|
|
2220
|
+
desc 'apps', 'List apps from App Store Connect and/or Google Play'
|
|
2237
2221
|
long_desc <<~DESC
|
|
2238
2222
|
List apps synced from app stores.
|
|
2239
2223
|
|
|
@@ -2268,8 +2252,8 @@ module Mysigner
|
|
|
2268
2252
|
|
|
2269
2253
|
# iOS Apps
|
|
2270
2254
|
if show_ios
|
|
2271
|
-
say
|
|
2272
|
-
say
|
|
2255
|
+
say '📱 iOS Apps', :cyan
|
|
2256
|
+
say ''
|
|
2273
2257
|
|
|
2274
2258
|
begin
|
|
2275
2259
|
response = client.get(
|
|
@@ -2279,78 +2263,78 @@ module Mysigner
|
|
|
2279
2263
|
ios_apps = response[:data]['data']&.fetch('apps', nil) || []
|
|
2280
2264
|
|
|
2281
2265
|
if ios_apps.empty?
|
|
2282
|
-
say
|
|
2283
|
-
say
|
|
2266
|
+
say ' No iOS apps found', :yellow
|
|
2267
|
+
say ''
|
|
2284
2268
|
say " Why don't my iOS apps appear?", :cyan
|
|
2285
|
-
say
|
|
2286
|
-
say
|
|
2287
|
-
say
|
|
2288
|
-
say
|
|
2289
|
-
say
|
|
2290
|
-
say
|
|
2291
|
-
say
|
|
2292
|
-
say
|
|
2293
|
-
say
|
|
2294
|
-
say
|
|
2295
|
-
say
|
|
2269
|
+
say ' ─────────────────────────────', :cyan
|
|
2270
|
+
say ''
|
|
2271
|
+
say ' Common reasons:', :yellow
|
|
2272
|
+
say ' • No apps registered in App Store Connect yet'
|
|
2273
|
+
say ' • Team ID not set on your credential'
|
|
2274
|
+
say ' • Bundle IDs exist but apps not created in App Store Connect'
|
|
2275
|
+
say ''
|
|
2276
|
+
say ' How to register an iOS app:', :cyan
|
|
2277
|
+
say ''
|
|
2278
|
+
say ' 1. Register a Bundle ID'
|
|
2279
|
+
say ' https://developer.apple.com/account/resources/identifiers/list'
|
|
2296
2280
|
say " Click '+' → App IDs → Enter your Bundle ID (e.g., com.company.appname)"
|
|
2297
|
-
say
|
|
2298
|
-
say
|
|
2299
|
-
say
|
|
2281
|
+
say ''
|
|
2282
|
+
say ' 2. Create the app in App Store Connect'
|
|
2283
|
+
say ' https://appstoreconnect.apple.com/apps'
|
|
2300
2284
|
say " My Apps → '+' → New App → Select your Bundle ID"
|
|
2301
|
-
say
|
|
2302
|
-
say
|
|
2303
|
-
say
|
|
2304
|
-
say
|
|
2305
|
-
say
|
|
2306
|
-
say
|
|
2285
|
+
say ''
|
|
2286
|
+
say ' 3. Sync your organization'
|
|
2287
|
+
say ' Run: ', :white
|
|
2288
|
+
say 'mysigner sync ios', :green
|
|
2289
|
+
say ''
|
|
2290
|
+
say ' 💡 Team ID tip:', :yellow
|
|
2307
2291
|
say " Apple's API doesn't expose Team ID. Set it manually in the web dashboard."
|
|
2308
|
-
say
|
|
2309
|
-
say
|
|
2292
|
+
say ' Find yours at: https://developer.apple.com/account/#!/membership/'
|
|
2293
|
+
say ''
|
|
2310
2294
|
else
|
|
2311
2295
|
ios_apps.each do |app|
|
|
2312
2296
|
say " • #{app['name'] || app['bundle_id']}", :green
|
|
2313
2297
|
say " Bundle ID: #{app['bundle_id']}"
|
|
2314
|
-
say
|
|
2298
|
+
say ''
|
|
2315
2299
|
end
|
|
2316
2300
|
end
|
|
2317
2301
|
rescue Mysigner::ClientError => e
|
|
2318
2302
|
say " Could not fetch iOS apps: #{e.message}", :yellow
|
|
2319
2303
|
end
|
|
2320
|
-
say
|
|
2304
|
+
say ''
|
|
2321
2305
|
end
|
|
2322
2306
|
|
|
2323
2307
|
# Android Apps
|
|
2324
|
-
|
|
2325
|
-
say "🤖 Android Apps", :cyan
|
|
2326
|
-
say ""
|
|
2308
|
+
return unless show_android
|
|
2327
2309
|
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
"/api/v1/organizations/#{config.current_organization_id}/android_apps",
|
|
2331
|
-
params: params
|
|
2332
|
-
)
|
|
2333
|
-
android_apps = response[:data]['android_apps'] || []
|
|
2310
|
+
say '🤖 Android Apps', :cyan
|
|
2311
|
+
say ''
|
|
2334
2312
|
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2313
|
+
begin
|
|
2314
|
+
response = client.get(
|
|
2315
|
+
"/api/v1/organizations/#{config.current_organization_id}/android_apps",
|
|
2316
|
+
params: params
|
|
2317
|
+
)
|
|
2318
|
+
android_apps = response[:data]['android_apps'] || []
|
|
2319
|
+
|
|
2320
|
+
if android_apps.empty?
|
|
2321
|
+
say ' No Android apps found', :yellow
|
|
2322
|
+
say ' Sync with: mysigner sync android', :yellow
|
|
2323
|
+
else
|
|
2324
|
+
android_apps.each do |app|
|
|
2325
|
+
say " • #{app['name'] || app['package_name']}", :green
|
|
2326
|
+
say " Package: #{app['package_name']}"
|
|
2327
|
+
say ''
|
|
2344
2328
|
end
|
|
2345
|
-
rescue Mysigner::ClientError => e
|
|
2346
|
-
say " Could not fetch Android apps: #{e.message}", :yellow
|
|
2347
2329
|
end
|
|
2330
|
+
rescue Mysigner::ClientError => e
|
|
2331
|
+
say " Could not fetch Android apps: #{e.message}", :yellow
|
|
2348
2332
|
end
|
|
2349
2333
|
end
|
|
2350
2334
|
|
|
2351
2335
|
# ==================== MERCHANT IDS (Apple Pay) ====================
|
|
2352
2336
|
|
|
2353
|
-
desc
|
|
2337
|
+
desc 'merchant-ids', 'List Apple Pay Merchant IDs'
|
|
2354
2338
|
method_option :search, type: :string, aliases: '-q', desc: 'Search by identifier or name'
|
|
2355
2339
|
method_option :page, type: :numeric, default: 1, desc: 'Page number'
|
|
2356
2340
|
method_option :per_page, type: :numeric, default: 50, desc: 'Items per page'
|
|
@@ -2358,8 +2342,8 @@ module Mysigner
|
|
|
2358
2342
|
config = load_config
|
|
2359
2343
|
client = create_client(config)
|
|
2360
2344
|
|
|
2361
|
-
say
|
|
2362
|
-
say
|
|
2345
|
+
say '💳 Merchant IDs', :cyan
|
|
2346
|
+
say ''
|
|
2363
2347
|
|
|
2364
2348
|
params = {
|
|
2365
2349
|
page: options[:page],
|
|
@@ -2376,19 +2360,20 @@ module Mysigner
|
|
|
2376
2360
|
pagination = response[:data]['pagination']
|
|
2377
2361
|
|
|
2378
2362
|
if merchant_ids.empty?
|
|
2379
|
-
say
|
|
2380
|
-
say
|
|
2381
|
-
say
|
|
2363
|
+
say ' No Merchant IDs found', :yellow
|
|
2364
|
+
say ''
|
|
2365
|
+
say ' Create one with: mysigner merchant-id create IDENTIFIER', :cyan
|
|
2382
2366
|
else
|
|
2383
2367
|
merchant_ids.each do |m|
|
|
2384
2368
|
say " • #{m['identifier']}", :green
|
|
2385
2369
|
say " Name: #{m['name']}" if m['name'] && m['name'] != m['identifier']
|
|
2386
2370
|
say " Team: #{m['team_id']}" if m['team_id']
|
|
2387
|
-
say
|
|
2371
|
+
say ''
|
|
2388
2372
|
end
|
|
2389
2373
|
|
|
2390
2374
|
if pagination
|
|
2391
|
-
say "Page #{pagination['page']} of #{pagination['total_pages']} (#{pagination['total']} total)",
|
|
2375
|
+
say "Page #{pagination['page']} of #{pagination['total_pages']} (#{pagination['total']} total)",
|
|
2376
|
+
:yellow
|
|
2392
2377
|
end
|
|
2393
2378
|
end
|
|
2394
2379
|
rescue Mysigner::ClientError => e
|
|
@@ -2397,7 +2382,7 @@ module Mysigner
|
|
|
2397
2382
|
end
|
|
2398
2383
|
end
|
|
2399
2384
|
|
|
2400
|
-
desc
|
|
2385
|
+
desc 'merchant-id SUBCOMMAND', 'Manage Apple Pay Merchant IDs'
|
|
2401
2386
|
long_desc <<~DESC
|
|
2402
2387
|
Create and delete Apple Pay Merchant IDs.
|
|
2403
2388
|
|
|
@@ -2423,21 +2408,21 @@ module Mysigner
|
|
|
2423
2408
|
case action
|
|
2424
2409
|
when 'create'
|
|
2425
2410
|
if identifier.nil?
|
|
2426
|
-
error
|
|
2427
|
-
say
|
|
2428
|
-
say
|
|
2411
|
+
error 'Usage: mysigner merchant-id create IDENTIFIER [--name NAME]'
|
|
2412
|
+
say ''
|
|
2413
|
+
say 'Example: mysigner merchant-id create merchant.com.company.app', :yellow
|
|
2429
2414
|
exit 1
|
|
2430
2415
|
end
|
|
2431
2416
|
|
|
2432
2417
|
unless identifier.start_with?('merchant.')
|
|
2433
2418
|
error "Merchant ID must start with 'merchant.'"
|
|
2434
|
-
say
|
|
2435
|
-
say
|
|
2419
|
+
say ''
|
|
2420
|
+
say 'Example: merchant.com.company.app', :cyan
|
|
2436
2421
|
exit 1
|
|
2437
2422
|
end
|
|
2438
2423
|
|
|
2439
|
-
say
|
|
2440
|
-
say
|
|
2424
|
+
say '💳 Creating Merchant ID...', :cyan
|
|
2425
|
+
say ''
|
|
2441
2426
|
|
|
2442
2427
|
begin
|
|
2443
2428
|
response = client.post(
|
|
@@ -2449,12 +2434,12 @@ module Mysigner
|
|
|
2449
2434
|
)
|
|
2450
2435
|
|
|
2451
2436
|
m = response[:data]['merchant_id'] || response[:data]
|
|
2452
|
-
say
|
|
2453
|
-
say
|
|
2437
|
+
say '✓ Merchant ID created successfully!', :green
|
|
2438
|
+
say ''
|
|
2454
2439
|
say " Identifier: #{m['identifier'] || identifier}", :white
|
|
2455
2440
|
say " Name: #{m['name']}", :white if m['name']
|
|
2456
2441
|
rescue Mysigner::ClientError => e
|
|
2457
|
-
if e.message.include?(
|
|
2442
|
+
if e.message.include?('already exists')
|
|
2458
2443
|
say "ℹ️ Merchant ID already exists: #{identifier}", :yellow
|
|
2459
2444
|
else
|
|
2460
2445
|
error "Failed to create Merchant ID: #{e.message}"
|
|
@@ -2464,12 +2449,12 @@ module Mysigner
|
|
|
2464
2449
|
|
|
2465
2450
|
when 'delete'
|
|
2466
2451
|
if identifier.nil?
|
|
2467
|
-
error
|
|
2452
|
+
error 'Usage: mysigner merchant-id delete IDENTIFIER'
|
|
2468
2453
|
exit 1
|
|
2469
2454
|
end
|
|
2470
2455
|
|
|
2471
|
-
say
|
|
2472
|
-
say
|
|
2456
|
+
say '💳 Deleting Merchant ID...', :cyan
|
|
2457
|
+
say ''
|
|
2473
2458
|
|
|
2474
2459
|
begin
|
|
2475
2460
|
# First find the merchant ID by identifier
|
|
@@ -2497,43 +2482,43 @@ module Mysigner
|
|
|
2497
2482
|
|
|
2498
2483
|
else
|
|
2499
2484
|
error "Unknown action: #{action}"
|
|
2500
|
-
say
|
|
2501
|
-
say
|
|
2502
|
-
say
|
|
2503
|
-
say
|
|
2485
|
+
say ''
|
|
2486
|
+
say 'Available actions:', :yellow
|
|
2487
|
+
say ' mysigner merchant-id create IDENTIFIER [--name NAME]', :cyan
|
|
2488
|
+
say ' mysigner merchant-id delete IDENTIFIER', :cyan
|
|
2504
2489
|
exit 1
|
|
2505
2490
|
end
|
|
2506
2491
|
end
|
|
2507
2492
|
|
|
2508
2493
|
# ==================== ANDROID TRACKS ====================
|
|
2509
2494
|
|
|
2510
|
-
desc
|
|
2495
|
+
desc 'tracks PACKAGE_NAME', 'List Google Play tracks for an Android app'
|
|
2511
2496
|
method_option :sort, type: :boolean, desc: 'Sort by track name'
|
|
2512
2497
|
def tracks(package_name = nil)
|
|
2513
2498
|
config = load_config
|
|
2514
2499
|
client = create_client(config)
|
|
2515
2500
|
|
|
2516
2501
|
if package_name.nil?
|
|
2517
|
-
error
|
|
2518
|
-
say
|
|
2519
|
-
say
|
|
2520
|
-
say
|
|
2521
|
-
say
|
|
2522
|
-
say
|
|
2502
|
+
error 'Usage: mysigner tracks PACKAGE_NAME'
|
|
2503
|
+
say ''
|
|
2504
|
+
say 'Example: mysigner tracks com.example.myapp', :yellow
|
|
2505
|
+
say ''
|
|
2506
|
+
say '💡 To see your registered Android apps:', :cyan
|
|
2507
|
+
say ' mysigner apps --platform android', :cyan
|
|
2523
2508
|
exit 1
|
|
2524
2509
|
end
|
|
2525
2510
|
|
|
2526
2511
|
say "🎯 Google Play Tracks for #{package_name}", :cyan
|
|
2527
|
-
say
|
|
2512
|
+
say ''
|
|
2528
2513
|
|
|
2529
2514
|
begin
|
|
2530
2515
|
response = client.get("/api/v1/organizations/#{config.current_organization_id}/android_apps/package/#{package_name}/tracks")
|
|
2531
2516
|
tracks = response[:data]['tracks'] || []
|
|
2532
2517
|
|
|
2533
2518
|
if tracks.empty?
|
|
2534
|
-
say
|
|
2535
|
-
say
|
|
2536
|
-
say
|
|
2519
|
+
say 'No tracks found', :yellow
|
|
2520
|
+
say ''
|
|
2521
|
+
say 'Tracks appear after you upload your app to Google Play Console', :cyan
|
|
2537
2522
|
say "and sync with: mysigner sync android --package #{package_name}", :cyan
|
|
2538
2523
|
return
|
|
2539
2524
|
end
|
|
@@ -2559,17 +2544,17 @@ module Mysigner
|
|
|
2559
2544
|
updated = Time.parse(track['updated_at']).strftime('%Y-%m-%d %H:%M')
|
|
2560
2545
|
say " Updated: #{updated}"
|
|
2561
2546
|
end
|
|
2562
|
-
say
|
|
2547
|
+
say ''
|
|
2563
2548
|
end
|
|
2564
2549
|
|
|
2565
2550
|
say "Total: #{tracks.count} track(s)", :yellow
|
|
2566
2551
|
rescue Mysigner::NotFoundError => e
|
|
2567
|
-
if e.message.include?(
|
|
2552
|
+
if e.message.include?('Android app')
|
|
2568
2553
|
error "Android app not found: #{package_name}"
|
|
2569
|
-
say
|
|
2570
|
-
say
|
|
2571
|
-
say
|
|
2572
|
-
say
|
|
2554
|
+
say ''
|
|
2555
|
+
say '💡 App not found:', :cyan
|
|
2556
|
+
say ' → Check the package name is correct', :yellow
|
|
2557
|
+
say ' → List your apps: mysigner apps --platform android', :yellow
|
|
2573
2558
|
say " → Register the app: mysigner android add #{package_name}", :yellow
|
|
2574
2559
|
else
|
|
2575
2560
|
error "Not found: #{e.message}"
|
|
@@ -2581,33 +2566,33 @@ module Mysigner
|
|
|
2581
2566
|
end
|
|
2582
2567
|
end
|
|
2583
2568
|
|
|
2584
|
-
desc
|
|
2569
|
+
desc 'track PACKAGE_NAME TRACK_NAME', 'Show details for a specific Google Play track'
|
|
2585
2570
|
def track(package_name = nil, track_name = nil)
|
|
2586
2571
|
config = load_config
|
|
2587
2572
|
client = create_client(config)
|
|
2588
2573
|
|
|
2589
2574
|
if package_name.nil? || track_name.nil?
|
|
2590
|
-
error
|
|
2591
|
-
say
|
|
2592
|
-
say
|
|
2593
|
-
say
|
|
2594
|
-
say
|
|
2595
|
-
say
|
|
2596
|
-
say
|
|
2597
|
-
say
|
|
2598
|
-
say
|
|
2575
|
+
error 'Usage: mysigner track PACKAGE_NAME TRACK_NAME'
|
|
2576
|
+
say ''
|
|
2577
|
+
say 'Example: mysigner track com.example.myapp production', :yellow
|
|
2578
|
+
say ' mysigner track com.example.myapp beta', :yellow
|
|
2579
|
+
say ''
|
|
2580
|
+
say 'Common track names: production, beta, alpha, internal', :cyan
|
|
2581
|
+
say ''
|
|
2582
|
+
say '💡 To see available tracks:', :cyan
|
|
2583
|
+
say ' mysigner tracks com.example.myapp', :cyan
|
|
2599
2584
|
exit 1
|
|
2600
2585
|
end
|
|
2601
2586
|
|
|
2602
2587
|
say "🎯 Track: #{track_name}", :cyan
|
|
2603
2588
|
say " Package: #{package_name}", :white
|
|
2604
|
-
say
|
|
2589
|
+
say ''
|
|
2605
2590
|
|
|
2606
2591
|
begin
|
|
2607
2592
|
response = client.get("/api/v1/organizations/#{config.current_organization_id}/android_apps/package/#{package_name}/tracks/#{track_name}")
|
|
2608
2593
|
track = response[:data]
|
|
2609
2594
|
|
|
2610
|
-
say
|
|
2595
|
+
say 'Details:', :bold
|
|
2611
2596
|
say " Track Name: #{track['track_name']}"
|
|
2612
2597
|
say " Status: #{track['status'] || 'unknown'}"
|
|
2613
2598
|
|
|
@@ -2617,9 +2602,9 @@ module Mysigner
|
|
|
2617
2602
|
end
|
|
2618
2603
|
|
|
2619
2604
|
# Show releases info
|
|
2605
|
+
say ''
|
|
2620
2606
|
if track['releases'].is_a?(Array) && track['releases'].any?
|
|
2621
|
-
say
|
|
2622
|
-
say "Releases:", :bold
|
|
2607
|
+
say 'Releases:', :bold
|
|
2623
2608
|
track['releases'].each_with_index do |release, idx|
|
|
2624
2609
|
say " Release #{idx + 1}:", :white
|
|
2625
2610
|
say " Status: #{release['status']}" if release['status']
|
|
@@ -2627,14 +2612,12 @@ module Mysigner
|
|
|
2627
2612
|
version_codes = release['versionCodes'] || release['version_codes'] || []
|
|
2628
2613
|
say " Version Codes: #{version_codes.join(', ')}" if version_codes.any?
|
|
2629
2614
|
|
|
2630
|
-
if release['name']
|
|
2631
|
-
say " Name: #{release['name']}"
|
|
2632
|
-
end
|
|
2615
|
+
say " Name: #{release['name']}" if release['name']
|
|
2633
2616
|
|
|
2634
2617
|
if release['releaseNotes'] || release['release_notes']
|
|
2635
2618
|
notes = release['releaseNotes'] || release['release_notes']
|
|
2636
2619
|
if notes.is_a?(Array) && notes.any?
|
|
2637
|
-
say
|
|
2620
|
+
say ' Release Notes:'
|
|
2638
2621
|
notes.each do |note|
|
|
2639
2622
|
lang = note['language'] || 'en-US'
|
|
2640
2623
|
text = note['text'] || ''
|
|
@@ -2649,24 +2632,22 @@ module Mysigner
|
|
|
2649
2632
|
end
|
|
2650
2633
|
end
|
|
2651
2634
|
else
|
|
2652
|
-
say
|
|
2653
|
-
say "No releases found in this track", :yellow
|
|
2635
|
+
say 'No releases found in this track', :yellow
|
|
2654
2636
|
end
|
|
2655
|
-
|
|
2656
2637
|
rescue Mysigner::NotFoundError => e
|
|
2657
|
-
if e.message.include?(
|
|
2638
|
+
if e.message.include?('Android app')
|
|
2658
2639
|
error "Android app not found: #{package_name}"
|
|
2659
|
-
say
|
|
2660
|
-
say
|
|
2661
|
-
say
|
|
2662
|
-
say
|
|
2663
|
-
elsif e.message.include?(
|
|
2640
|
+
say ''
|
|
2641
|
+
say '💡 App not found:', :cyan
|
|
2642
|
+
say ' → Check the package name is correct', :yellow
|
|
2643
|
+
say ' → List your apps: mysigner apps --platform android', :yellow
|
|
2644
|
+
elsif e.message.include?('Track')
|
|
2664
2645
|
error "Track not found: #{track_name}"
|
|
2665
|
-
say
|
|
2666
|
-
say
|
|
2667
|
-
say
|
|
2646
|
+
say ''
|
|
2647
|
+
say '💡 Track not found:', :cyan
|
|
2648
|
+
say ' → Check the track name is correct', :yellow
|
|
2668
2649
|
say " → List available tracks: mysigner tracks #{package_name}", :yellow
|
|
2669
|
-
say
|
|
2650
|
+
say ' → Common tracks: production, beta, alpha, internal', :yellow
|
|
2670
2651
|
else
|
|
2671
2652
|
error "Not found: #{e.message}"
|
|
2672
2653
|
end
|
|
@@ -2679,7 +2660,7 @@ module Mysigner
|
|
|
2679
2660
|
|
|
2680
2661
|
# ==================== APP GROUPS ====================
|
|
2681
2662
|
|
|
2682
|
-
desc
|
|
2663
|
+
desc 'app-groups', 'List App Groups'
|
|
2683
2664
|
method_option :search, type: :string, aliases: '-q', desc: 'Search by identifier or name'
|
|
2684
2665
|
method_option :page, type: :numeric, default: 1, desc: 'Page number'
|
|
2685
2666
|
method_option :per_page, type: :numeric, default: 50, desc: 'Items per page'
|
|
@@ -2687,8 +2668,8 @@ module Mysigner
|
|
|
2687
2668
|
config = load_config
|
|
2688
2669
|
client = create_client(config)
|
|
2689
2670
|
|
|
2690
|
-
say
|
|
2691
|
-
say
|
|
2671
|
+
say '📦 App Groups', :cyan
|
|
2672
|
+
say ''
|
|
2692
2673
|
|
|
2693
2674
|
params = {
|
|
2694
2675
|
page: options[:page],
|
|
@@ -2705,21 +2686,22 @@ module Mysigner
|
|
|
2705
2686
|
pagination = response[:data]['pagination']
|
|
2706
2687
|
|
|
2707
2688
|
if app_groups.empty?
|
|
2708
|
-
say
|
|
2709
|
-
say
|
|
2710
|
-
say
|
|
2711
|
-
say
|
|
2712
|
-
say
|
|
2689
|
+
say ' No App Groups found', :yellow
|
|
2690
|
+
say ''
|
|
2691
|
+
say ' Register one with: mysigner app-group register IDENTIFIER', :cyan
|
|
2692
|
+
say ''
|
|
2693
|
+
say ' Note: App Groups must first be created in Apple Developer Portal', :yellow
|
|
2713
2694
|
else
|
|
2714
2695
|
app_groups.each do |g|
|
|
2715
2696
|
say " • #{g['identifier']}", :green
|
|
2716
2697
|
say " Name: #{g['name']}" if g['name'] && g['name'] != g['identifier']
|
|
2717
2698
|
say " Team: #{g['team_id']}" if g['team_id']
|
|
2718
|
-
say
|
|
2699
|
+
say ''
|
|
2719
2700
|
end
|
|
2720
2701
|
|
|
2721
2702
|
if pagination
|
|
2722
|
-
say "Page #{pagination['page']} of #{pagination['total_pages']} (#{pagination['total']} total)",
|
|
2703
|
+
say "Page #{pagination['page']} of #{pagination['total_pages']} (#{pagination['total']} total)",
|
|
2704
|
+
:yellow
|
|
2723
2705
|
end
|
|
2724
2706
|
end
|
|
2725
2707
|
rescue Mysigner::ClientError => e
|
|
@@ -2728,7 +2710,7 @@ module Mysigner
|
|
|
2728
2710
|
end
|
|
2729
2711
|
end
|
|
2730
2712
|
|
|
2731
|
-
desc
|
|
2713
|
+
desc 'app-group SUBCOMMAND', 'Manage App Groups'
|
|
2732
2714
|
long_desc <<~DESC
|
|
2733
2715
|
Register and delete App Groups.
|
|
2734
2716
|
|
|
@@ -2758,23 +2740,23 @@ module Mysigner
|
|
|
2758
2740
|
case action
|
|
2759
2741
|
when 'register'
|
|
2760
2742
|
if identifier.nil?
|
|
2761
|
-
error
|
|
2762
|
-
say
|
|
2763
|
-
say
|
|
2764
|
-
say
|
|
2765
|
-
say
|
|
2743
|
+
error 'Usage: mysigner app-group register IDENTIFIER [--name NAME]'
|
|
2744
|
+
say ''
|
|
2745
|
+
say 'Example: mysigner app-group register group.com.company.shared', :yellow
|
|
2746
|
+
say ''
|
|
2747
|
+
say 'Note: Create the App Group in Apple Developer Portal first!', :cyan
|
|
2766
2748
|
exit 1
|
|
2767
2749
|
end
|
|
2768
2750
|
|
|
2769
2751
|
unless identifier.start_with?('group.')
|
|
2770
2752
|
error "App Group identifier must start with 'group.'"
|
|
2771
|
-
say
|
|
2772
|
-
say
|
|
2753
|
+
say ''
|
|
2754
|
+
say 'Example: group.com.company.shared', :cyan
|
|
2773
2755
|
exit 1
|
|
2774
2756
|
end
|
|
2775
2757
|
|
|
2776
|
-
say
|
|
2777
|
-
say
|
|
2758
|
+
say '📦 Registering App Group...', :cyan
|
|
2759
|
+
say ''
|
|
2778
2760
|
|
|
2779
2761
|
begin
|
|
2780
2762
|
response = client.post(
|
|
@@ -2786,15 +2768,15 @@ module Mysigner
|
|
|
2786
2768
|
)
|
|
2787
2769
|
|
|
2788
2770
|
g = response[:data]['app_group'] || response[:data]
|
|
2789
|
-
say
|
|
2790
|
-
say
|
|
2771
|
+
say '✓ App Group registered!', :green
|
|
2772
|
+
say ''
|
|
2791
2773
|
say " Identifier: #{g['identifier'] || identifier}", :white
|
|
2792
2774
|
say " Name: #{g['name']}", :white if g['name']
|
|
2793
|
-
say
|
|
2794
|
-
say
|
|
2795
|
-
say
|
|
2775
|
+
say ''
|
|
2776
|
+
say ' Remember: This only registers the App Group in My Signer.', :yellow
|
|
2777
|
+
say ' Make sure it exists in Apple Developer Portal.', :yellow
|
|
2796
2778
|
rescue Mysigner::ClientError => e
|
|
2797
|
-
if e.message.include?(
|
|
2779
|
+
if e.message.include?('already exists')
|
|
2798
2780
|
say "ℹ️ App Group already registered: #{identifier}", :yellow
|
|
2799
2781
|
else
|
|
2800
2782
|
error "Failed to register App Group: #{e.message}"
|
|
@@ -2804,12 +2786,12 @@ module Mysigner
|
|
|
2804
2786
|
|
|
2805
2787
|
when 'delete'
|
|
2806
2788
|
if identifier.nil?
|
|
2807
|
-
error
|
|
2789
|
+
error 'Usage: mysigner app-group delete IDENTIFIER'
|
|
2808
2790
|
exit 1
|
|
2809
2791
|
end
|
|
2810
2792
|
|
|
2811
|
-
say
|
|
2812
|
-
say
|
|
2793
|
+
say '📦 Removing App Group...', :cyan
|
|
2794
|
+
say ''
|
|
2813
2795
|
|
|
2814
2796
|
begin
|
|
2815
2797
|
# First find the app group by identifier
|
|
@@ -2830,9 +2812,9 @@ module Mysigner
|
|
|
2830
2812
|
)
|
|
2831
2813
|
|
|
2832
2814
|
say "✓ App Group removed from My Signer: #{identifier}", :green
|
|
2833
|
-
say
|
|
2834
|
-
say
|
|
2835
|
-
say
|
|
2815
|
+
say ''
|
|
2816
|
+
say ' Note: The App Group still exists in Apple Developer Portal.', :yellow
|
|
2817
|
+
say ' Delete it manually if needed.', :yellow
|
|
2836
2818
|
rescue Mysigner::ClientError => e
|
|
2837
2819
|
error "Failed to remove App Group: #{e.message}"
|
|
2838
2820
|
exit 1
|
|
@@ -2840,17 +2822,17 @@ module Mysigner
|
|
|
2840
2822
|
|
|
2841
2823
|
else
|
|
2842
2824
|
error "Unknown action: #{action}"
|
|
2843
|
-
say
|
|
2844
|
-
say
|
|
2845
|
-
say
|
|
2846
|
-
say
|
|
2825
|
+
say ''
|
|
2826
|
+
say 'Available actions:', :yellow
|
|
2827
|
+
say ' mysigner app-group register IDENTIFIER [--name NAME]', :cyan
|
|
2828
|
+
say ' mysigner app-group delete IDENTIFIER', :cyan
|
|
2847
2829
|
exit 1
|
|
2848
2830
|
end
|
|
2849
2831
|
end
|
|
2850
2832
|
|
|
2851
2833
|
# ==================== GOOGLE PLAY CREDENTIALS ====================
|
|
2852
2834
|
|
|
2853
|
-
desc
|
|
2835
|
+
desc 'gp-credential SUBCOMMAND', 'Manage Google Play credentials (list, delete, activate, test)'
|
|
2854
2836
|
long_desc <<~DESC
|
|
2855
2837
|
Manage Google Play API credentials for Android app distribution.
|
|
2856
2838
|
|
|
@@ -2888,17 +2870,17 @@ module Mysigner
|
|
|
2888
2870
|
|
|
2889
2871
|
case action
|
|
2890
2872
|
when 'list'
|
|
2891
|
-
say
|
|
2892
|
-
say
|
|
2873
|
+
say '🔑 Google Play Credentials', :cyan
|
|
2874
|
+
say ''
|
|
2893
2875
|
|
|
2894
2876
|
begin
|
|
2895
2877
|
response = client.get("/api/v1/organizations/#{config.current_organization_id}/google_play_credentials")
|
|
2896
2878
|
credentials = response[:data]['google_play_credentials'] || []
|
|
2897
2879
|
|
|
2898
2880
|
if credentials.empty?
|
|
2899
|
-
say
|
|
2900
|
-
say
|
|
2901
|
-
say
|
|
2881
|
+
say 'No Google Play credentials found', :yellow
|
|
2882
|
+
say ''
|
|
2883
|
+
say 'Set up credentials with: mysigner onboard', :yellow
|
|
2902
2884
|
return
|
|
2903
2885
|
end
|
|
2904
2886
|
|
|
@@ -2917,7 +2899,7 @@ module Mysigner
|
|
|
2917
2899
|
sync_color = cred['last_sync_status'] == 'success' ? :green : :red
|
|
2918
2900
|
say " Sync Status: #{cred['last_sync_status']}", sync_color
|
|
2919
2901
|
end
|
|
2920
|
-
say
|
|
2902
|
+
say ''
|
|
2921
2903
|
end
|
|
2922
2904
|
|
|
2923
2905
|
say "Total: #{credentials.count} credential(s)", :yellow
|
|
@@ -2930,23 +2912,23 @@ module Mysigner
|
|
|
2930
2912
|
credential_id = args[0]
|
|
2931
2913
|
|
|
2932
2914
|
unless credential_id
|
|
2933
|
-
error
|
|
2934
|
-
say
|
|
2915
|
+
error 'Usage: mysigner gp-credential delete ID'
|
|
2916
|
+
say ''
|
|
2935
2917
|
say "Run 'mysigner gp-credential list' to see available IDs", :yellow
|
|
2936
2918
|
exit 1
|
|
2937
2919
|
end
|
|
2938
2920
|
|
|
2939
2921
|
say "⚠️ You are about to delete Google Play credential ID: #{credential_id}", :yellow
|
|
2940
|
-
say
|
|
2922
|
+
say ''
|
|
2941
2923
|
|
|
2942
|
-
if yes?(
|
|
2924
|
+
if yes?('Are you sure? This cannot be undone. (y/n)')
|
|
2943
2925
|
begin
|
|
2944
2926
|
client.delete("/api/v1/organizations/#{config.current_organization_id}/google_play_credentials/#{credential_id}")
|
|
2945
|
-
say
|
|
2946
|
-
say
|
|
2927
|
+
say ''
|
|
2928
|
+
say '✓ Google Play credential deleted', :green
|
|
2947
2929
|
rescue Mysigner::NotFoundError
|
|
2948
2930
|
error "Credential not found with ID: #{credential_id}"
|
|
2949
|
-
say
|
|
2931
|
+
say ''
|
|
2950
2932
|
say "Run 'mysigner gp-credential list' to see available IDs", :yellow
|
|
2951
2933
|
exit 1
|
|
2952
2934
|
rescue Mysigner::ClientError => e
|
|
@@ -2954,30 +2936,30 @@ module Mysigner
|
|
|
2954
2936
|
exit 1
|
|
2955
2937
|
end
|
|
2956
2938
|
else
|
|
2957
|
-
say
|
|
2939
|
+
say 'Deletion cancelled', :yellow
|
|
2958
2940
|
end
|
|
2959
2941
|
|
|
2960
2942
|
when 'activate'
|
|
2961
2943
|
credential_id = args[0]
|
|
2962
2944
|
|
|
2963
2945
|
unless credential_id
|
|
2964
|
-
error
|
|
2965
|
-
say
|
|
2946
|
+
error 'Usage: mysigner gp-credential activate ID'
|
|
2947
|
+
say ''
|
|
2966
2948
|
say "Run 'mysigner gp-credential list' to see available IDs", :yellow
|
|
2967
2949
|
exit 1
|
|
2968
2950
|
end
|
|
2969
2951
|
|
|
2970
|
-
say
|
|
2952
|
+
say '🔑 Activating credential...', :cyan
|
|
2971
2953
|
|
|
2972
2954
|
begin
|
|
2973
2955
|
response = client.post("/api/v1/organizations/#{config.current_organization_id}/google_play_credentials/#{credential_id}/activate")
|
|
2974
2956
|
credential = response[:data]['google_play_credential'] || response[:data]
|
|
2975
|
-
say
|
|
2976
|
-
say
|
|
2957
|
+
say '✓ Credential activated!', :green
|
|
2958
|
+
say ''
|
|
2977
2959
|
say "#{credential['name']} is now the active Google Play credential", :cyan
|
|
2978
2960
|
rescue Mysigner::NotFoundError
|
|
2979
2961
|
error "Credential not found with ID: #{credential_id}"
|
|
2980
|
-
say
|
|
2962
|
+
say ''
|
|
2981
2963
|
say "Run 'mysigner gp-credential list' to see available IDs", :yellow
|
|
2982
2964
|
exit 1
|
|
2983
2965
|
rescue Mysigner::ClientError => e
|
|
@@ -2989,37 +2971,37 @@ module Mysigner
|
|
|
2989
2971
|
credential_id = args[0]
|
|
2990
2972
|
|
|
2991
2973
|
unless credential_id
|
|
2992
|
-
error
|
|
2993
|
-
say
|
|
2974
|
+
error 'Usage: mysigner gp-credential test ID'
|
|
2975
|
+
say ''
|
|
2994
2976
|
say "Run 'mysigner gp-credential list' to see available IDs", :yellow
|
|
2995
2977
|
exit 1
|
|
2996
2978
|
end
|
|
2997
2979
|
|
|
2998
|
-
say
|
|
2999
|
-
say
|
|
2980
|
+
say '🔑 Testing credential connection...', :cyan
|
|
2981
|
+
say ''
|
|
3000
2982
|
|
|
3001
2983
|
begin
|
|
3002
2984
|
response = client.post("/api/v1/organizations/#{config.current_organization_id}/google_play_credentials/#{credential_id}/test")
|
|
3003
2985
|
result = response[:data]
|
|
3004
2986
|
|
|
3005
2987
|
if result['success']
|
|
3006
|
-
say
|
|
3007
|
-
say
|
|
3008
|
-
say
|
|
2988
|
+
say '✓ Connection successful!', :green
|
|
2989
|
+
say ''
|
|
2990
|
+
say ' Google Play API is reachable with this credential', :cyan
|
|
3009
2991
|
else
|
|
3010
|
-
say
|
|
3011
|
-
say
|
|
2992
|
+
say '✗ Connection failed', :red
|
|
2993
|
+
say ''
|
|
3012
2994
|
say " Error: #{result['error']}", :red if result['error']
|
|
3013
|
-
say
|
|
3014
|
-
say
|
|
3015
|
-
say
|
|
3016
|
-
say
|
|
3017
|
-
say
|
|
2995
|
+
say ''
|
|
2996
|
+
say '💡 Check that:', :cyan
|
|
2997
|
+
say ' → The service account JSON key is valid', :yellow
|
|
2998
|
+
say ' → The service account has Google Play Console access', :yellow
|
|
2999
|
+
say ' → API access is enabled in Google Play Console', :yellow
|
|
3018
3000
|
exit 1
|
|
3019
3001
|
end
|
|
3020
3002
|
rescue Mysigner::NotFoundError
|
|
3021
3003
|
error "Credential not found with ID: #{credential_id}"
|
|
3022
|
-
say
|
|
3004
|
+
say ''
|
|
3023
3005
|
say "Run 'mysigner gp-credential list' to see available IDs", :yellow
|
|
3024
3006
|
exit 1
|
|
3025
3007
|
rescue Mysigner::ClientError => e
|
|
@@ -3031,14 +3013,14 @@ module Mysigner
|
|
|
3031
3013
|
invoke :help, ['gp-credential']
|
|
3032
3014
|
else
|
|
3033
3015
|
error "Unknown action: #{action}"
|
|
3034
|
-
say
|
|
3016
|
+
say 'Available actions: list, delete, activate, test, help', :yellow
|
|
3035
3017
|
exit 1
|
|
3036
3018
|
end
|
|
3037
3019
|
end
|
|
3038
3020
|
|
|
3039
3021
|
# ==================== APP STORE RELEASES ====================
|
|
3040
3022
|
|
|
3041
|
-
desc
|
|
3023
|
+
desc 'release SUBCOMMAND', 'Manage App Store release configurations (list, show, create, update)'
|
|
3042
3024
|
long_desc <<~DESC
|
|
3043
3025
|
Manage App Store release configurations for iOS app distribution.
|
|
3044
3026
|
|
|
@@ -3107,8 +3089,8 @@ module Mysigner
|
|
|
3107
3089
|
|
|
3108
3090
|
case action
|
|
3109
3091
|
when 'list'
|
|
3110
|
-
say
|
|
3111
|
-
say
|
|
3092
|
+
say '🚀 App Store Releases', :cyan
|
|
3093
|
+
say ''
|
|
3112
3094
|
|
|
3113
3095
|
params = {}
|
|
3114
3096
|
params[:bundle_id] = options[:bundle_id] if options[:bundle_id]
|
|
@@ -3121,9 +3103,9 @@ module Mysigner
|
|
|
3121
3103
|
releases = response[:data]['app_store_releases'] || []
|
|
3122
3104
|
|
|
3123
3105
|
if releases.empty?
|
|
3124
|
-
say
|
|
3125
|
-
say
|
|
3126
|
-
say
|
|
3106
|
+
say 'No release configurations found', :yellow
|
|
3107
|
+
say ''
|
|
3108
|
+
say 'Create one with: mysigner release create --bundle-id-id ID', :yellow
|
|
3127
3109
|
return
|
|
3128
3110
|
end
|
|
3129
3111
|
|
|
@@ -3134,7 +3116,7 @@ module Mysigner
|
|
|
3134
3116
|
say " Auto Submit: #{rel['auto_submit'] ? 'Yes' : 'No'}"
|
|
3135
3117
|
say " Phased Release: #{rel['phased_release'] ? 'Yes' : 'No'}"
|
|
3136
3118
|
say " Version: #{rel['version_string']}" if rel['version_string']
|
|
3137
|
-
say
|
|
3119
|
+
say ''
|
|
3138
3120
|
end
|
|
3139
3121
|
|
|
3140
3122
|
say "Total: #{releases.count} release(s)", :yellow
|
|
@@ -3147,8 +3129,8 @@ module Mysigner
|
|
|
3147
3129
|
release_id = args[0]
|
|
3148
3130
|
|
|
3149
3131
|
unless release_id
|
|
3150
|
-
error
|
|
3151
|
-
say
|
|
3132
|
+
error 'Usage: mysigner release show ID'
|
|
3133
|
+
say ''
|
|
3152
3134
|
say "Run 'mysigner release list' to see available IDs", :yellow
|
|
3153
3135
|
exit 1
|
|
3154
3136
|
end
|
|
@@ -3158,31 +3140,31 @@ module Mysigner
|
|
|
3158
3140
|
rel = response[:data]['app_store_release'] || response[:data]
|
|
3159
3141
|
|
|
3160
3142
|
say "🚀 Release Configuration (ID: #{rel['id']})", :cyan
|
|
3161
|
-
say
|
|
3162
|
-
say
|
|
3143
|
+
say ''
|
|
3144
|
+
say 'Details:', :bold
|
|
3163
3145
|
say " App Name: #{rel['app_name'] || 'N/A'}"
|
|
3164
3146
|
say " Bundle ID: #{rel['bundle_identifier'] || 'N/A'}"
|
|
3165
3147
|
say " Version: #{rel['version_string'] || 'N/A'}"
|
|
3166
3148
|
say " Release Type: #{rel['release_type'] || 'N/A'}"
|
|
3167
3149
|
say " Auto Submit: #{rel['auto_submit'] ? 'Yes' : 'No'}"
|
|
3168
3150
|
say " Phased Release: #{rel['phased_release'] ? 'Yes' : 'No'}"
|
|
3169
|
-
say
|
|
3151
|
+
say ''
|
|
3170
3152
|
if rel['whats_new'] && !rel['whats_new'].empty?
|
|
3171
3153
|
say "What's New:", :bold
|
|
3172
3154
|
say " #{rel['whats_new']}"
|
|
3173
|
-
say
|
|
3155
|
+
say ''
|
|
3174
3156
|
end
|
|
3175
|
-
say
|
|
3157
|
+
say 'URLs:', :bold
|
|
3176
3158
|
say " Support: #{rel['support_url'] || 'N/A'}"
|
|
3177
3159
|
say " Marketing: #{rel['marketing_url'] || 'N/A'}"
|
|
3178
3160
|
say " Privacy: #{rel['privacy_url'] || 'N/A'}"
|
|
3179
3161
|
if rel['scheduled_date']
|
|
3180
|
-
say
|
|
3162
|
+
say ''
|
|
3181
3163
|
say "Scheduled Date: #{rel['scheduled_date']}"
|
|
3182
3164
|
end
|
|
3183
3165
|
rescue Mysigner::NotFoundError
|
|
3184
3166
|
error "Release not found with ID: #{release_id}"
|
|
3185
|
-
say
|
|
3167
|
+
say ''
|
|
3186
3168
|
say "Run 'mysigner release list' to see available IDs", :yellow
|
|
3187
3169
|
exit 1
|
|
3188
3170
|
rescue Mysigner::ClientError => e
|
|
@@ -3191,8 +3173,8 @@ module Mysigner
|
|
|
3191
3173
|
end
|
|
3192
3174
|
|
|
3193
3175
|
when 'create'
|
|
3194
|
-
say
|
|
3195
|
-
say
|
|
3176
|
+
say '🚀 Creating release configuration...', :cyan
|
|
3177
|
+
say ''
|
|
3196
3178
|
|
|
3197
3179
|
body = {}
|
|
3198
3180
|
body[:bundle_id_id] = options[:bundle_id_id] if options[:bundle_id_id]
|
|
@@ -3212,9 +3194,9 @@ module Mysigner
|
|
|
3212
3194
|
)
|
|
3213
3195
|
rel = response[:data]['app_store_release'] || response[:data]
|
|
3214
3196
|
|
|
3215
|
-
say
|
|
3216
|
-
say
|
|
3217
|
-
say
|
|
3197
|
+
say '✓ Release configuration created!', :green
|
|
3198
|
+
say ''
|
|
3199
|
+
say 'Details:', :bold
|
|
3218
3200
|
say " ID: #{rel['id']}"
|
|
3219
3201
|
say " Bundle ID: #{rel['bundle_identifier'] || 'N/A'}"
|
|
3220
3202
|
say " Release Type: #{rel['release_type'] || 'N/A'}"
|
|
@@ -3222,24 +3204,22 @@ module Mysigner
|
|
|
3222
3204
|
say " Phased Release: #{rel['phased_release'] ? 'Yes' : 'No'}"
|
|
3223
3205
|
rescue Mysigner::ValidationError => e
|
|
3224
3206
|
if e.message.include?('already exists') || (e.error_code && e.error_code.to_s == '409')
|
|
3225
|
-
error
|
|
3226
|
-
say
|
|
3207
|
+
error 'Release configuration already exists for this bundle ID'
|
|
3208
|
+
say ''
|
|
3227
3209
|
say "💡 Use 'mysigner release update ID' to modify it", :cyan
|
|
3228
3210
|
say " Run 'mysigner release list' to find the ID", :yellow
|
|
3229
3211
|
else
|
|
3230
3212
|
error "Validation failed: #{e.message}"
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
say " #{field}: #{errors_text}", :red
|
|
3235
|
-
end
|
|
3213
|
+
e.details&.each do |field, errors|
|
|
3214
|
+
errors_text = errors.is_a?(Array) ? errors.join(', ') : errors.to_s
|
|
3215
|
+
say " #{field}: #{errors_text}", :red
|
|
3236
3216
|
end
|
|
3237
3217
|
end
|
|
3238
3218
|
exit 1
|
|
3239
3219
|
rescue Mysigner::ClientError => e
|
|
3240
3220
|
if e.message.include?('409') || e.message.include?('already exists')
|
|
3241
|
-
error
|
|
3242
|
-
say
|
|
3221
|
+
error 'Release configuration already exists for this bundle ID'
|
|
3222
|
+
say ''
|
|
3243
3223
|
say "💡 Use 'mysigner release update ID' to modify it", :cyan
|
|
3244
3224
|
say " Run 'mysigner release list' to find the ID", :yellow
|
|
3245
3225
|
else
|
|
@@ -3252,8 +3232,8 @@ module Mysigner
|
|
|
3252
3232
|
release_id = args[0]
|
|
3253
3233
|
|
|
3254
3234
|
unless release_id
|
|
3255
|
-
error
|
|
3256
|
-
say
|
|
3235
|
+
error 'Usage: mysigner release update ID [OPTIONS]'
|
|
3236
|
+
say ''
|
|
3257
3237
|
say "Run 'mysigner release list' to see available IDs", :yellow
|
|
3258
3238
|
exit 1
|
|
3259
3239
|
end
|
|
@@ -3268,8 +3248,8 @@ module Mysigner
|
|
|
3268
3248
|
body[:release_type] = options[:release_type] if options[:release_type]
|
|
3269
3249
|
body[:scheduled_date] = options[:scheduled_date] if options[:scheduled_date]
|
|
3270
3250
|
|
|
3271
|
-
say
|
|
3272
|
-
say
|
|
3251
|
+
say '🚀 Updating release configuration...', :cyan
|
|
3252
|
+
say ''
|
|
3273
3253
|
|
|
3274
3254
|
begin
|
|
3275
3255
|
response = client.patch(
|
|
@@ -3278,9 +3258,9 @@ module Mysigner
|
|
|
3278
3258
|
)
|
|
3279
3259
|
rel = response[:data]['app_store_release'] || response[:data]
|
|
3280
3260
|
|
|
3281
|
-
say
|
|
3282
|
-
say
|
|
3283
|
-
say
|
|
3261
|
+
say '✓ Release configuration updated!', :green
|
|
3262
|
+
say ''
|
|
3263
|
+
say 'Details:', :bold
|
|
3284
3264
|
say " ID: #{rel['id']}"
|
|
3285
3265
|
say " Bundle ID: #{rel['bundle_identifier'] || 'N/A'}"
|
|
3286
3266
|
say " Release Type: #{rel['release_type'] || 'N/A'}"
|
|
@@ -3288,16 +3268,14 @@ module Mysigner
|
|
|
3288
3268
|
say " Phased Release: #{rel['phased_release'] ? 'Yes' : 'No'}"
|
|
3289
3269
|
rescue Mysigner::NotFoundError
|
|
3290
3270
|
error "Release not found with ID: #{release_id}"
|
|
3291
|
-
say
|
|
3271
|
+
say ''
|
|
3292
3272
|
say "Run 'mysigner release list' to see available IDs", :yellow
|
|
3293
3273
|
exit 1
|
|
3294
3274
|
rescue Mysigner::ValidationError => e
|
|
3295
3275
|
error "Validation failed: #{e.message}"
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
say " #{field}: #{errors_text}", :red
|
|
3300
|
-
end
|
|
3276
|
+
e.details&.each do |field, errors|
|
|
3277
|
+
errors_text = errors.is_a?(Array) ? errors.join(', ') : errors.to_s
|
|
3278
|
+
say " #{field}: #{errors_text}", :red
|
|
3301
3279
|
end
|
|
3302
3280
|
exit 1
|
|
3303
3281
|
rescue Mysigner::ClientError => e
|
|
@@ -3309,7 +3287,7 @@ module Mysigner
|
|
|
3309
3287
|
invoke :help, ['release']
|
|
3310
3288
|
else
|
|
3311
3289
|
error "Unknown action: #{action}"
|
|
3312
|
-
say
|
|
3290
|
+
say 'Available actions: list, show, create, update, help', :yellow
|
|
3313
3291
|
exit 1
|
|
3314
3292
|
end
|
|
3315
3293
|
end
|