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,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Mysigner
|
|
2
4
|
module Upload
|
|
3
5
|
class AppStoreAutomation
|
|
@@ -11,23 +13,23 @@ module Mysigner
|
|
|
11
13
|
def initialize(client:, organization_id:, opts: {})
|
|
12
14
|
@client = client
|
|
13
15
|
@organization_id = organization_id
|
|
14
|
-
@wait_enabled = opts.key?(:wait) ?
|
|
16
|
+
@wait_enabled = opts.key?(:wait) ? !opts[:wait].nil? : true
|
|
15
17
|
|
|
16
18
|
poll = opts[:poll_interval] || opts[:poll_seconds]
|
|
17
19
|
poll = poll.to_i if poll
|
|
18
|
-
@poll_interval = poll
|
|
20
|
+
@poll_interval = poll&.positive? ? poll : DEFAULT_POLL_INTERVAL
|
|
19
21
|
|
|
20
22
|
timeout = opts[:timeout] || opts[:timeout_seconds]
|
|
21
23
|
timeout = timeout.to_i if timeout
|
|
22
|
-
@timeout = timeout
|
|
24
|
+
@timeout = timeout&.positive? ? timeout : DEFAULT_WAIT_TIMEOUT
|
|
23
25
|
|
|
24
|
-
@no_submit =
|
|
26
|
+
@no_submit = !opts[:no_submit].nil?
|
|
25
27
|
@now = opts[:now]
|
|
26
28
|
end
|
|
27
29
|
|
|
28
30
|
def perform!(metadata:, build_info:, metadata_overrides: {})
|
|
29
31
|
build_info = symbolize_keys(build_info)
|
|
30
|
-
metadata
|
|
32
|
+
metadata ||= {}
|
|
31
33
|
|
|
32
34
|
result = {
|
|
33
35
|
wait: {
|
|
@@ -43,9 +45,9 @@ module Mysigner
|
|
|
43
45
|
submission_source: nil
|
|
44
46
|
}
|
|
45
47
|
|
|
46
|
-
puts
|
|
47
|
-
puts
|
|
48
|
-
puts
|
|
48
|
+
puts ''
|
|
49
|
+
puts '🤖 App Store automation in progress...'
|
|
50
|
+
puts ''
|
|
49
51
|
|
|
50
52
|
app = ensure_app(build_info[:bundle_id])
|
|
51
53
|
raise AutomationError, "App with bundle ID #{build_info[:bundle_id]} not found" unless app
|
|
@@ -56,11 +58,13 @@ module Mysigner
|
|
|
56
58
|
unless build
|
|
57
59
|
version_info = [build_info[:version], build_info[:build_number]].compact.join(' / ')
|
|
58
60
|
version_info = "for #{version_info}" unless version_info.empty?
|
|
59
|
-
raise AutomationError,
|
|
61
|
+
raise AutomationError,
|
|
62
|
+
"No processed build found #{version_info}. Upload a build first with 'mysigner ship appstore --wait'"
|
|
60
63
|
end
|
|
61
64
|
|
|
62
65
|
unless build_processed?(build)
|
|
63
|
-
raise AutomationError,
|
|
66
|
+
raise AutomationError,
|
|
67
|
+
"Build #{build_info[:version]} (#{build_info[:build_number]}) is still processing. Wait for it or use --wait flag."
|
|
64
68
|
end
|
|
65
69
|
|
|
66
70
|
version = ensure_app_store_version(app_id: app['id'], metadata: metadata, overrides: metadata_overrides)
|
|
@@ -70,12 +74,12 @@ module Mysigner
|
|
|
70
74
|
|
|
71
75
|
if should_submit
|
|
72
76
|
submit_for_review(
|
|
73
|
-
version_id: version['id'],
|
|
77
|
+
version_id: version['id'],
|
|
74
78
|
version_string: version['version_string'],
|
|
75
|
-
metadata: metadata,
|
|
79
|
+
metadata: metadata,
|
|
76
80
|
overrides: metadata_overrides
|
|
77
81
|
)
|
|
78
|
-
puts
|
|
82
|
+
puts '✓ Submitted for App Store review'
|
|
79
83
|
result[:submitted] = true
|
|
80
84
|
result[:submission_source] = submit_source
|
|
81
85
|
else
|
|
@@ -83,8 +87,8 @@ module Mysigner
|
|
|
83
87
|
result[:skip_reason] = skip_reason
|
|
84
88
|
end
|
|
85
89
|
|
|
86
|
-
puts
|
|
87
|
-
puts
|
|
90
|
+
puts ''
|
|
91
|
+
puts '✅ App Store automation complete'
|
|
88
92
|
|
|
89
93
|
result
|
|
90
94
|
end
|
|
@@ -112,29 +116,29 @@ module Mysigner
|
|
|
112
116
|
|
|
113
117
|
return [build, status] unless @wait_enabled
|
|
114
118
|
|
|
115
|
-
puts
|
|
119
|
+
puts '⏳ Waiting for Apple to finish processing the build...'
|
|
116
120
|
puts " Polling every #{@poll_interval}s (timeout #{@timeout}s)"
|
|
117
|
-
print
|
|
121
|
+
print ''
|
|
118
122
|
|
|
119
123
|
start_time = current_time
|
|
120
|
-
|
|
124
|
+
|
|
121
125
|
loop do
|
|
122
126
|
build = latest_build(app_id, build_info)
|
|
123
127
|
status[:last_state] = build_state(build)
|
|
124
128
|
|
|
125
129
|
if build && build_processed?(build)
|
|
126
130
|
puts "\r✓ Build is processed and ready".ljust(70)
|
|
127
|
-
puts
|
|
131
|
+
puts ''
|
|
128
132
|
return [build, status]
|
|
129
133
|
end
|
|
130
|
-
|
|
134
|
+
|
|
131
135
|
elapsed = current_time - start_time
|
|
132
136
|
status[:elapsed_seconds] = elapsed
|
|
133
137
|
|
|
134
138
|
if elapsed >= @timeout
|
|
135
139
|
status[:timed_out] = true
|
|
136
140
|
puts "\r✗ Timed out after #{format_duration(elapsed)} (use --asc-timeout-seconds to extend)".ljust(90)
|
|
137
|
-
puts
|
|
141
|
+
puts ''
|
|
138
142
|
return [build, status]
|
|
139
143
|
end
|
|
140
144
|
|
|
@@ -152,32 +156,30 @@ module Mysigner
|
|
|
152
156
|
app_id: app_id,
|
|
153
157
|
processed_only: !@wait_enabled
|
|
154
158
|
}
|
|
155
|
-
|
|
159
|
+
|
|
156
160
|
# Only filter by version/build if NOT using latest
|
|
157
161
|
unless build_info[:use_latest]
|
|
158
162
|
params[:version] = build_info[:version] if build_info[:version]
|
|
159
163
|
params[:build_number] = build_info[:build_number] if build_info[:build_number]
|
|
160
164
|
end
|
|
161
|
-
|
|
165
|
+
|
|
162
166
|
# Apply min_build_number filter if set in release config
|
|
163
|
-
if build_info[:min_build_number]
|
|
164
|
-
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
+
params[:min_build_number] = build_info[:min_build_number] if build_info[:min_build_number]
|
|
168
|
+
|
|
167
169
|
response = @client.get(
|
|
168
170
|
"/api/v1/organizations/#{@organization_id}/builds",
|
|
169
171
|
params: params.compact
|
|
170
172
|
)
|
|
171
173
|
|
|
172
174
|
builds = Array(response[:data]['data']['builds'])
|
|
173
|
-
|
|
175
|
+
|
|
174
176
|
# Smart build selection: filter by min_build_number client-side if API doesn't support it
|
|
175
177
|
if build_info[:min_build_number] && builds.any?
|
|
176
178
|
min_bn = build_info[:min_build_number].to_i
|
|
177
179
|
builds = builds.select { |b| b['build_number'].to_i >= min_bn }
|
|
178
180
|
end
|
|
179
|
-
|
|
180
|
-
builds.first
|
|
181
|
+
|
|
182
|
+
builds.first # Already ordered by uploaded_date desc
|
|
181
183
|
rescue Mysigner::NotFoundError
|
|
182
184
|
nil
|
|
183
185
|
rescue StandardError => e
|
|
@@ -264,7 +266,7 @@ module Mysigner
|
|
|
264
266
|
def determine_earliest_release_date(metadata, overrides)
|
|
265
267
|
date = overrides['earliest_release_date'] || metadata['earliest_release_date']
|
|
266
268
|
return nil unless date
|
|
267
|
-
|
|
269
|
+
|
|
268
270
|
# Convert to ISO 8601 if not already
|
|
269
271
|
date.respond_to?(:iso8601) ? date.iso8601 : date.to_s
|
|
270
272
|
end
|
|
@@ -287,17 +289,17 @@ module Mysigner
|
|
|
287
289
|
|
|
288
290
|
def determine_release_type(metadata, overrides)
|
|
289
291
|
result = overrides['release_type'] || metadata['release_type']
|
|
290
|
-
|
|
292
|
+
|
|
291
293
|
# FIX v7: Changed default from MANUAL to AFTER_APPROVAL
|
|
292
294
|
# Log deprecation notice if no explicit release_type is set
|
|
293
295
|
if result.nil?
|
|
294
296
|
if ENV['MYSIGNER_DEBUG'] || @deprecation_warned.nil?
|
|
295
|
-
puts
|
|
297
|
+
puts ' Note: Using default release_type AFTER_APPROVAL (was MANUAL in older versions)'
|
|
296
298
|
@deprecation_warned = true
|
|
297
299
|
end
|
|
298
300
|
result = 'AFTER_APPROVAL'
|
|
299
301
|
end
|
|
300
|
-
|
|
302
|
+
|
|
301
303
|
result
|
|
302
304
|
end
|
|
303
305
|
|
|
@@ -307,7 +309,7 @@ module Mysigner
|
|
|
307
309
|
body: { build_id: build_id }
|
|
308
310
|
)
|
|
309
311
|
|
|
310
|
-
puts
|
|
312
|
+
puts '✓ Attached build to App Store version'
|
|
311
313
|
rescue StandardError => e
|
|
312
314
|
raise AutomationError, "Failed to attach build to version: #{e.message}"
|
|
313
315
|
end
|
|
@@ -316,11 +318,13 @@ module Mysigner
|
|
|
316
318
|
return [false, nil, '--no-auto-submit flag'] if @no_submit
|
|
317
319
|
|
|
318
320
|
if overrides.key?('auto_submit')
|
|
319
|
-
return overrides['auto_submit'] ? [true, 'CLI override',
|
|
321
|
+
return overrides['auto_submit'] ? [true, 'CLI override',
|
|
322
|
+
nil] : [false, nil, 'CLI override disabled auto_submit']
|
|
320
323
|
end
|
|
321
324
|
|
|
322
325
|
if metadata.key?('auto_submit')
|
|
323
|
-
return metadata['auto_submit'] ? [true, 'Dashboard configuration',
|
|
326
|
+
return metadata['auto_submit'] ? [true, 'Dashboard configuration',
|
|
327
|
+
nil] : [false, nil, 'Dashboard auto_submit disabled']
|
|
324
328
|
end
|
|
325
329
|
|
|
326
330
|
[false, nil, 'No auto_submit configuration']
|
|
@@ -329,27 +333,26 @@ module Mysigner
|
|
|
329
333
|
def submit_for_review(version_id:, version_string: nil, metadata: {}, overrides: {})
|
|
330
334
|
# Merge metadata and overrides
|
|
331
335
|
merged = metadata.merge(overrides)
|
|
332
|
-
|
|
336
|
+
|
|
333
337
|
# Get version string to check if first version
|
|
334
338
|
version_string ||= merged['version_string'] || merged['version'] || '1.0'
|
|
335
339
|
is_first_version = version_string.split('.').first.to_i <= 1
|
|
336
|
-
|
|
340
|
+
|
|
337
341
|
# Validate required fields for Apple submission
|
|
338
342
|
# Note: What's New is NOT required for version 1.0 (first release)
|
|
339
343
|
# Support URL may already be set in App Store Connect, so we only warn if missing
|
|
340
344
|
missing_fields = []
|
|
341
345
|
missing_fields << "What's New text" if !is_first_version && merged['whats_new'].to_s.strip.empty?
|
|
342
|
-
|
|
346
|
+
|
|
343
347
|
# Don't block on missing support_url - it may already be in App Store Connect
|
|
344
348
|
# Just warn about it
|
|
345
|
-
if merged['support_url'].to_s.strip.empty?
|
|
346
|
-
|
|
347
|
-
end
|
|
348
|
-
|
|
349
|
+
puts '⚠️ Note: No Support URL provided via CLI - using value from App Store Connect if available' if merged['support_url'].to_s.strip.empty?
|
|
350
|
+
|
|
349
351
|
unless missing_fields.empty?
|
|
350
|
-
raise AutomationError,
|
|
352
|
+
raise AutomationError,
|
|
353
|
+
"Cannot submit to Apple Store: missing required fields: #{missing_fields.join(', ')}. Please configure these in your My Signer dashboard or provide via --whats-new flag."
|
|
351
354
|
end
|
|
352
|
-
|
|
355
|
+
|
|
353
356
|
payload = {
|
|
354
357
|
whats_new: merged['whats_new'],
|
|
355
358
|
keywords: merged['keywords'],
|
|
@@ -357,7 +360,7 @@ module Mysigner
|
|
|
357
360
|
promotional_text: merged['promotional_text'],
|
|
358
361
|
support_url: merged['support_url'],
|
|
359
362
|
locale: merged['locale']
|
|
360
|
-
}.compact
|
|
363
|
+
}.compact # Remove nil values
|
|
361
364
|
|
|
362
365
|
@client.post(
|
|
363
366
|
"/api/v1/organizations/#{@organization_id}/app_store_versions/#{version_id}/submit",
|
|
@@ -375,7 +378,6 @@ module Mysigner
|
|
|
375
378
|
end
|
|
376
379
|
end
|
|
377
380
|
|
|
378
|
-
|
|
379
381
|
def format_duration(seconds)
|
|
380
382
|
minutes = (seconds / 60).floor
|
|
381
383
|
seconds = (seconds % 60).round
|
|
@@ -383,7 +385,7 @@ module Mysigner
|
|
|
383
385
|
end
|
|
384
386
|
|
|
385
387
|
def current_time
|
|
386
|
-
|
|
388
|
+
@now&.call || Time.now
|
|
387
389
|
end
|
|
388
390
|
end
|
|
389
391
|
end
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Mysigner
|
|
4
4
|
module Upload
|
|
@@ -16,52 +16,47 @@ module Mysigner
|
|
|
16
16
|
|
|
17
17
|
# Submit build for App Store review
|
|
18
18
|
def submit_for_review!(automation: nil)
|
|
19
|
-
puts
|
|
20
|
-
puts
|
|
21
|
-
puts
|
|
22
|
-
|
|
19
|
+
puts ''
|
|
20
|
+
puts '📤 Preparing for App Store submission...'
|
|
21
|
+
puts ''
|
|
22
|
+
|
|
23
23
|
begin
|
|
24
24
|
# Step 1: Fetch release metadata from My Signer API
|
|
25
25
|
merged = merge_metadata(fetch_release_metadata)
|
|
26
26
|
metadata = merged[:merged]
|
|
27
|
-
|
|
28
|
-
if metadata
|
|
29
|
-
puts
|
|
30
|
-
puts
|
|
27
|
+
|
|
28
|
+
if metadata&.any?
|
|
29
|
+
puts '✓ Loaded release configuration from My Signer'
|
|
30
|
+
puts ''
|
|
31
31
|
display_metadata(metadata)
|
|
32
32
|
else
|
|
33
|
-
puts
|
|
33
|
+
puts '⚠️ No release configuration found in My Signer'
|
|
34
34
|
puts " Create one at: #{@client.api_url}/organizations/#{@organization_id}/app_store_releases"
|
|
35
|
-
puts
|
|
35
|
+
puts ''
|
|
36
36
|
end
|
|
37
|
-
|
|
37
|
+
|
|
38
38
|
# Enrich build_info with config values for smart build selection
|
|
39
39
|
enriched_build_info = symbolize_keys(@build_info)
|
|
40
40
|
if metadata
|
|
41
41
|
# min_build_number: skip builds below this number
|
|
42
|
-
if metadata['build_number'] && !enriched_build_info[:build_number]
|
|
43
|
-
enriched_build_info[:min_build_number] = metadata['build_number'].to_i
|
|
44
|
-
end
|
|
42
|
+
enriched_build_info[:min_build_number] = metadata['build_number'].to_i if metadata['build_number'] && !enriched_build_info[:build_number]
|
|
45
43
|
# Use version_string from config if not specified
|
|
46
|
-
if metadata['version_string'] && !enriched_build_info[:version]
|
|
47
|
-
enriched_build_info[:version] = metadata['version_string']
|
|
48
|
-
end
|
|
44
|
+
enriched_build_info[:version] = metadata['version_string'] if metadata['version_string'] && !enriched_build_info[:version]
|
|
49
45
|
end
|
|
50
|
-
|
|
46
|
+
|
|
51
47
|
automation_result = if automation
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
48
|
+
automation.perform!(
|
|
49
|
+
metadata: metadata,
|
|
50
|
+
build_info: enriched_build_info,
|
|
51
|
+
metadata_overrides: @metadata_overrides
|
|
52
|
+
)
|
|
53
|
+
else
|
|
54
|
+
guide_to_manual_submission(metadata)
|
|
55
|
+
nil
|
|
56
|
+
end
|
|
61
57
|
|
|
62
58
|
{ success: true, metadata: metadata, automation: automation_result }
|
|
63
|
-
|
|
64
|
-
rescue => e
|
|
59
|
+
rescue StandardError => e
|
|
65
60
|
raise SubmissionError, "Failed to prepare submission: #{e.message}"
|
|
66
61
|
end
|
|
67
62
|
end
|
|
@@ -70,40 +65,37 @@ module Mysigner
|
|
|
70
65
|
|
|
71
66
|
def fetch_release_metadata
|
|
72
67
|
# Fetch release metadata from My Signer API
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
else
|
|
88
|
-
data
|
|
89
|
-
end
|
|
68
|
+
|
|
69
|
+
response = @client.get(
|
|
70
|
+
"/api/v1/organizations/#{@organization_id}/app_store_releases",
|
|
71
|
+
params: { bundle_id: @build_info[:bundle_id] }
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
if response[:success]
|
|
75
|
+
data = response[:data]
|
|
76
|
+
# API returns { app_store_releases: [...] } - extract first release
|
|
77
|
+
if data.is_a?(Hash) && data['app_store_releases'].is_a?(Array)
|
|
78
|
+
data['app_store_releases'].first
|
|
79
|
+
elsif data.is_a?(Hash) && data['app_store_release']
|
|
80
|
+
# Single release format
|
|
81
|
+
data['app_store_release']
|
|
90
82
|
else
|
|
91
|
-
|
|
83
|
+
data
|
|
92
84
|
end
|
|
93
|
-
rescue Mysigner::NotFoundError
|
|
94
|
-
# No configuration found - that's okay
|
|
95
|
-
nil
|
|
96
|
-
rescue StandardError => e
|
|
97
|
-
puts "⚠️ Could not fetch release metadata: #{e.message}"
|
|
98
|
-
nil
|
|
99
85
|
end
|
|
86
|
+
rescue Mysigner::NotFoundError
|
|
87
|
+
# No configuration found - that's okay
|
|
88
|
+
nil
|
|
89
|
+
rescue StandardError => e
|
|
90
|
+
puts "⚠️ Could not fetch release metadata: #{e.message}"
|
|
91
|
+
nil
|
|
100
92
|
end
|
|
101
93
|
|
|
102
94
|
# Fetch release config and extract min_build_number for smart build selection
|
|
103
95
|
def fetch_release_config
|
|
104
96
|
metadata = fetch_release_metadata
|
|
105
97
|
return {} unless metadata
|
|
106
|
-
|
|
98
|
+
|
|
107
99
|
config = {}
|
|
108
100
|
config[:min_build_number] = metadata['build_number'].to_i if metadata['build_number']
|
|
109
101
|
config[:release_type] = metadata['release_type'] if metadata['release_type']
|
|
@@ -122,39 +114,40 @@ module Mysigner
|
|
|
122
114
|
end
|
|
123
115
|
|
|
124
116
|
def guide_to_manual_submission(metadata)
|
|
125
|
-
puts
|
|
126
|
-
puts
|
|
127
|
-
puts
|
|
128
|
-
puts
|
|
129
|
-
puts
|
|
130
|
-
puts
|
|
131
|
-
puts
|
|
132
|
-
puts
|
|
133
|
-
puts
|
|
117
|
+
puts '📋 Next Steps for App Store Submission'
|
|
118
|
+
puts '=' * 60
|
|
119
|
+
puts ''
|
|
120
|
+
puts 'Your build is uploaded to App Store Connect!'
|
|
121
|
+
puts ''
|
|
122
|
+
puts 'To submit for review:'
|
|
123
|
+
puts ' 1. Wait for Apple to process the build (5-15 minutes)'
|
|
124
|
+
puts ' 2. Open App Store Connect:'
|
|
125
|
+
puts ' https://appstoreconnect.apple.com'
|
|
134
126
|
puts " 3. Select your app and go to 'App Store' tab"
|
|
135
|
-
puts
|
|
127
|
+
puts ' 4. Create a new version or select existing one'
|
|
136
128
|
puts " 5. Select this build (#{@build_info[:version]} / #{@build_info[:build_number]})"
|
|
137
|
-
puts
|
|
129
|
+
puts ' 6. Add screenshots and metadata if not already present'
|
|
138
130
|
puts " 7. Click 'Submit for Review'"
|
|
139
|
-
puts
|
|
131
|
+
puts ''
|
|
140
132
|
|
|
141
|
-
if metadata && metadata['auto_submit']
|
|
142
|
-
puts "💡 Auto-submit enabled"
|
|
143
|
-
end
|
|
133
|
+
puts '💡 Auto-submit enabled' if metadata && metadata['auto_submit']
|
|
144
134
|
|
|
145
|
-
puts
|
|
146
|
-
puts
|
|
147
|
-
puts
|
|
148
|
-
puts
|
|
135
|
+
puts ''
|
|
136
|
+
puts 'Tip: rerun with --submit-for-review when ready'
|
|
137
|
+
puts ' Use --wait/--asc-timeout-seconds to control polling'
|
|
138
|
+
puts ''
|
|
149
139
|
end
|
|
150
140
|
|
|
151
141
|
def display_metadata(metadata)
|
|
152
|
-
puts
|
|
142
|
+
puts '📝 Release Configuration:'
|
|
153
143
|
print_metadata_line('Bundle ID', metadata['bundle_identifier'], 'bundle_identifier')
|
|
154
144
|
print_metadata_line('App Name', metadata['app_name'], 'app_name') if metadata['app_name']
|
|
155
|
-
|
|
145
|
+
|
|
156
146
|
# Version info from config
|
|
157
|
-
|
|
147
|
+
if metadata['version_string']
|
|
148
|
+
print_metadata_line('Version String', metadata['version_string'],
|
|
149
|
+
'version_string')
|
|
150
|
+
end
|
|
158
151
|
print_metadata_line('Min Build #', metadata['build_number'], 'build_number') if metadata['build_number']
|
|
159
152
|
|
|
160
153
|
if metadata['whats_new'] && !metadata['whats_new'].to_s.strip.empty?
|
|
@@ -162,7 +155,7 @@ module Mysigner
|
|
|
162
155
|
else
|
|
163
156
|
puts " What's New: —#{override_suffix('whats_new')}"
|
|
164
157
|
end
|
|
165
|
-
|
|
158
|
+
|
|
166
159
|
if metadata['promotional_text'] && !metadata['promotional_text'].to_s.strip.empty?
|
|
167
160
|
puts " Promo Text: #{truncate(metadata['promotional_text'])}#{override_suffix('promotional_text')}"
|
|
168
161
|
end
|
|
@@ -170,14 +163,14 @@ module Mysigner
|
|
|
170
163
|
print_metadata_line('Support URL', metadata['support_url'], 'support_url')
|
|
171
164
|
print_metadata_line('Marketing URL', metadata['marketing_url'], 'marketing_url')
|
|
172
165
|
print_metadata_line('Privacy URL', metadata['privacy_policy_url'], 'privacy_policy_url')
|
|
173
|
-
|
|
166
|
+
|
|
174
167
|
# Release settings
|
|
175
168
|
release_type_label = format_release_type(metadata['release_type'])
|
|
176
169
|
print_metadata_line('Release Type', release_type_label, 'release_type')
|
|
177
170
|
if metadata['release_type'] == 'SCHEDULED' && metadata['earliest_release_date']
|
|
178
171
|
print_metadata_line('Scheduled Date', metadata['earliest_release_date'], 'earliest_release_date')
|
|
179
172
|
end
|
|
180
|
-
|
|
173
|
+
|
|
181
174
|
print_metadata_toggle('Auto-submit', metadata['auto_submit'], 'auto_submit')
|
|
182
175
|
print_metadata_toggle('Phased Release', metadata['phased_release'], 'phased_release')
|
|
183
176
|
|
|
@@ -188,9 +181,9 @@ module Mysigner
|
|
|
188
181
|
end
|
|
189
182
|
|
|
190
183
|
warn_missing_submission_fields(metadata)
|
|
191
|
-
puts
|
|
184
|
+
puts ''
|
|
192
185
|
end
|
|
193
|
-
|
|
186
|
+
|
|
194
187
|
def format_release_type(release_type)
|
|
195
188
|
case release_type
|
|
196
189
|
when 'AFTER_APPROVAL' then 'After Approval (auto-release)'
|
|
@@ -205,10 +198,10 @@ module Mysigner
|
|
|
205
198
|
|
|
206
199
|
overrides.each do |key, value|
|
|
207
200
|
merged[key] = if merged[key].is_a?(Hash) && value.is_a?(Hash)
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
201
|
+
deep_merge(merged[key], value)
|
|
202
|
+
else
|
|
203
|
+
value
|
|
204
|
+
end
|
|
212
205
|
end
|
|
213
206
|
|
|
214
207
|
merged
|
|
@@ -253,7 +246,11 @@ module Mysigner
|
|
|
253
246
|
end
|
|
254
247
|
|
|
255
248
|
def print_metadata_toggle(label, value, key)
|
|
256
|
-
human = value.nil?
|
|
249
|
+
human = if value.nil?
|
|
250
|
+
'—'
|
|
251
|
+
else
|
|
252
|
+
(value ? 'Yes' : 'No')
|
|
253
|
+
end
|
|
257
254
|
puts " #{label}: #{human}#{override_suffix(key)}"
|
|
258
255
|
end
|
|
259
256
|
|
|
@@ -294,19 +291,17 @@ module Mysigner
|
|
|
294
291
|
warnings = []
|
|
295
292
|
# What's New only required for updates (version > 1.0)
|
|
296
293
|
warnings << "Missing What's New copy (required for version updates)" if !is_first_version && metadata['whats_new'].to_s.strip.empty?
|
|
297
|
-
|
|
294
|
+
|
|
298
295
|
# Support URL may already be in App Store Connect, so just note it
|
|
299
296
|
if metadata['support_url'].to_s.strip.empty?
|
|
300
|
-
warnings <<
|
|
297
|
+
warnings << 'Support URL not configured in My Signer (will use App Store Connect value if available)'
|
|
301
298
|
end
|
|
302
299
|
|
|
303
300
|
return if warnings.empty?
|
|
304
301
|
|
|
305
|
-
puts
|
|
302
|
+
puts ' ⚠️ Notes:' unless warnings.empty?
|
|
306
303
|
warnings.each { |msg| puts " - #{msg}" }
|
|
307
304
|
end
|
|
308
305
|
end
|
|
309
306
|
end
|
|
310
307
|
end
|
|
311
|
-
|
|
312
|
-
|