mysigner 0.1.1 ā 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.githooks/pre-commit +15 -0
- data/.githooks/pre-push +21 -0
- data/.github/workflows/ci.yml +29 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +55 -0
- data/.rubocop_todo.yml +112 -0
- data/CHANGELOG.md +96 -0
- data/Gemfile +5 -3
- data/Gemfile.lock +38 -8
- data/README.md +87 -17
- data/Rakefile +5 -3
- data/bin/console +4 -3
- data/bin/setup +3 -0
- data/exe/mysigner +2 -1
- data/lib/mysigner/build/android_executor.rb +46 -52
- data/lib/mysigner/build/android_parser.rb +33 -40
- data/lib/mysigner/build/configurator.rb +17 -16
- data/lib/mysigner/build/detector.rb +39 -50
- data/lib/mysigner/build/error_analyzer.rb +70 -68
- data/lib/mysigner/build/executor.rb +30 -37
- data/lib/mysigner/build/parser.rb +18 -18
- data/lib/mysigner/cli/auth_commands.rb +735 -752
- data/lib/mysigner/cli/build_commands.rb +697 -721
- data/lib/mysigner/cli/concerns/actionable_suggestions.rb +208 -154
- data/lib/mysigner/cli/concerns/api_helpers.rb +46 -54
- data/lib/mysigner/cli/concerns/error_handlers.rb +247 -237
- data/lib/mysigner/cli/concerns/helpers.rb +12 -1
- data/lib/mysigner/cli/diagnostic_commands.rb +659 -635
- data/lib/mysigner/cli/resource_commands.rb +1266 -822
- data/lib/mysigner/cli/validate_commands.rb +161 -0
- data/lib/mysigner/cli.rb +5 -1
- data/lib/mysigner/client.rb +27 -19
- data/lib/mysigner/config.rb +93 -56
- data/lib/mysigner/export/exporter.rb +32 -36
- data/lib/mysigner/signing/certificate_checker.rb +18 -23
- data/lib/mysigner/signing/keystore_manager.rb +34 -39
- data/lib/mysigner/signing/validator.rb +38 -40
- data/lib/mysigner/signing/wizard.rb +329 -342
- data/lib/mysigner/upload/app_store_automation.rb +51 -49
- data/lib/mysigner/upload/app_store_submission.rb +87 -92
- data/lib/mysigner/upload/play_store_uploader.rb +98 -115
- data/lib/mysigner/upload/uploader.rb +101 -109
- data/lib/mysigner/version.rb +3 -1
- data/lib/mysigner.rb +13 -11
- data/mysigner.gemspec +36 -33
- data/test_manual.rb +37 -36
- metadata +38 -16
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'json'
|
|
2
4
|
require 'stringio'
|
|
3
5
|
|
|
@@ -7,12 +9,12 @@ module Mysigner
|
|
|
7
9
|
class UploadError < Mysigner::Error; end
|
|
8
10
|
class CredentialsError < UploadError; end
|
|
9
11
|
class TrackError < UploadError; end
|
|
10
|
-
|
|
12
|
+
|
|
11
13
|
# Special error for when AAB uploaded but track assignment failed
|
|
12
14
|
# This carries the version_code so it can be saved to prevent conflicts
|
|
13
15
|
class PartialUploadError < UploadError
|
|
14
16
|
attr_reader :version_code
|
|
15
|
-
|
|
17
|
+
|
|
16
18
|
def initialize(message, version_code:)
|
|
17
19
|
super(message)
|
|
18
20
|
@version_code = version_code
|
|
@@ -20,7 +22,7 @@ module Mysigner
|
|
|
20
22
|
end
|
|
21
23
|
|
|
22
24
|
VALID_TRACKS = %w[internal alpha beta production].freeze
|
|
23
|
-
SCOPE = 'https://www.googleapis.com/auth/androidpublisher'
|
|
25
|
+
SCOPE = 'https://www.googleapis.com/auth/androidpublisher'
|
|
24
26
|
|
|
25
27
|
def initialize(aab_path:, service_account_json:, package_name:)
|
|
26
28
|
@aab_path = File.expand_path(aab_path)
|
|
@@ -37,11 +39,11 @@ module Mysigner
|
|
|
37
39
|
# @param user_fraction [Float] Rollout percentage (0.0-1.0) for staged rollouts
|
|
38
40
|
# @return [Hash] Upload result with version_code and track info
|
|
39
41
|
def upload!(track: 'internal', release_notes: nil, user_fraction: nil)
|
|
40
|
-
@current_track = track
|
|
42
|
+
@current_track = track # Store for error messages
|
|
41
43
|
say_uploading(track)
|
|
42
44
|
|
|
43
45
|
version_code = nil
|
|
44
|
-
|
|
46
|
+
|
|
45
47
|
begin
|
|
46
48
|
# 1. Create an edit
|
|
47
49
|
edit = create_edit
|
|
@@ -53,9 +55,7 @@ module Mysigner
|
|
|
53
55
|
say_upload_success(version_code)
|
|
54
56
|
|
|
55
57
|
# 3. Assign to track with release
|
|
56
|
-
if track
|
|
57
|
-
assign_to_track(edit.id, track, version_code, release_notes: release_notes, user_fraction: user_fraction)
|
|
58
|
-
end
|
|
58
|
+
assign_to_track(edit.id, track, version_code, release_notes: release_notes, user_fraction: user_fraction) if track
|
|
59
59
|
|
|
60
60
|
# 4. Commit the edit
|
|
61
61
|
commit_edit(edit.id)
|
|
@@ -71,20 +71,16 @@ module Mysigner
|
|
|
71
71
|
rescue Google::Apis::ClientError => e
|
|
72
72
|
error_message = parse_google_error(e)
|
|
73
73
|
# If AAB was uploaded, raise PartialUploadError so CLI can save the version
|
|
74
|
-
if version_code
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
raise UploadError, "Google Play API error: #{error_message}"
|
|
78
|
-
end
|
|
74
|
+
raise PartialUploadError.new("Google Play API error: #{error_message}", version_code: version_code) if version_code
|
|
75
|
+
|
|
76
|
+
raise UploadError, "Google Play API error: #{error_message}"
|
|
79
77
|
rescue PartialUploadError
|
|
80
78
|
# Re-raise as-is
|
|
81
79
|
raise
|
|
82
|
-
rescue => e
|
|
83
|
-
if version_code
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
raise UploadError, "Upload failed: #{e.message}"
|
|
87
|
-
end
|
|
80
|
+
rescue StandardError => e
|
|
81
|
+
raise PartialUploadError.new("Upload failed: #{e.message}", version_code: version_code) if version_code
|
|
82
|
+
|
|
83
|
+
raise UploadError, "Upload failed: #{e.message}"
|
|
88
84
|
end
|
|
89
85
|
end
|
|
90
86
|
|
|
@@ -110,14 +106,14 @@ module Mysigner
|
|
|
110
106
|
rescue Google::Apis::ClientError => e
|
|
111
107
|
error_message = parse_google_error(e)
|
|
112
108
|
raise UploadError, "Google Play API error: #{error_message}"
|
|
113
|
-
rescue => e
|
|
109
|
+
rescue StandardError => e
|
|
114
110
|
raise UploadError, "Upload failed: #{e.message}"
|
|
115
111
|
end
|
|
116
112
|
end
|
|
117
113
|
|
|
118
114
|
# Assign an existing version code to a track
|
|
119
115
|
def assign_existing_to_track!(version_code, track:, release_notes: nil, user_fraction: nil)
|
|
120
|
-
@current_track = track
|
|
116
|
+
@current_track = track # Store for error messages
|
|
121
117
|
begin
|
|
122
118
|
edit = create_edit
|
|
123
119
|
assign_to_track(edit.id, track, version_code, release_notes: release_notes, user_fraction: user_fraction)
|
|
@@ -138,18 +134,14 @@ module Mysigner
|
|
|
138
134
|
private
|
|
139
135
|
|
|
140
136
|
def validate_aab!
|
|
141
|
-
unless File.exist?(@aab_path)
|
|
142
|
-
raise UploadError, "AAB file not found: #{@aab_path}"
|
|
143
|
-
end
|
|
137
|
+
raise UploadError, "AAB file not found: #{@aab_path}" unless File.exist?(@aab_path)
|
|
144
138
|
|
|
145
|
-
unless @aab_path.end_with?('.aab')
|
|
146
|
-
raise UploadError, "Invalid file type: #{@aab_path} (must be .aab)"
|
|
147
|
-
end
|
|
139
|
+
raise UploadError, "Invalid file type: #{@aab_path} (must be .aab)" unless @aab_path.end_with?('.aab')
|
|
148
140
|
|
|
149
141
|
file_size = File.size(@aab_path)
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
142
|
+
return unless file_size < 10_000
|
|
143
|
+
|
|
144
|
+
raise UploadError, "AAB file seems too small: #{file_size} bytes (possible corruption)"
|
|
153
145
|
end
|
|
154
146
|
|
|
155
147
|
def setup_google_client!
|
|
@@ -173,20 +165,18 @@ module Mysigner
|
|
|
173
165
|
@service = Google::Apis::AndroidpublisherV3::AndroidPublisherService.new
|
|
174
166
|
@service.authorization = @auth
|
|
175
167
|
@service.client_options.open_timeout_sec = 30
|
|
176
|
-
@service.client_options.read_timeout_sec = 300
|
|
168
|
+
@service.client_options.read_timeout_sec = 300 # Large file uploads need time
|
|
177
169
|
@service.request_options.retries = 3
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
raise CredentialsError, "Google API client not installed. Run: gem install google-api-client"
|
|
170
|
+
rescue LoadError
|
|
171
|
+
raise CredentialsError, 'Google API client not installed. Run: gem install google-api-client'
|
|
181
172
|
end
|
|
182
173
|
|
|
183
174
|
def create_edit
|
|
184
175
|
edit = Google::Apis::AndroidpublisherV3::AppEdit.new
|
|
185
176
|
@service.insert_edit(@package_name, edit)
|
|
186
177
|
rescue Google::Apis::ClientError => e
|
|
187
|
-
if e.message.include?(
|
|
188
|
-
|
|
189
|
-
end
|
|
178
|
+
raise UploadError, first_upload_error_message if e.message.include?('Package not found') || e.status_code == 404
|
|
179
|
+
|
|
190
180
|
raise
|
|
191
181
|
end
|
|
192
182
|
|
|
@@ -209,28 +199,25 @@ module Mysigner
|
|
|
209
199
|
|
|
210
200
|
def upload_bundle(edit_id)
|
|
211
201
|
puts "š¦ Uploading AAB (#{format_bytes(File.size(@aab_path))})..."
|
|
212
|
-
puts
|
|
202
|
+
puts ''
|
|
213
203
|
|
|
214
204
|
begin
|
|
215
|
-
|
|
205
|
+
@service.upload_edit_bundle(
|
|
216
206
|
@package_name,
|
|
217
207
|
edit_id,
|
|
218
208
|
upload_source: @aab_path,
|
|
219
209
|
content_type: 'application/octet-stream'
|
|
220
210
|
)
|
|
221
|
-
result
|
|
222
211
|
rescue Google::Apis::ClientError => e
|
|
223
212
|
error_msg = parse_google_error(e)
|
|
224
213
|
raise UploadError, "Bundle upload failed: #{error_msg}"
|
|
225
|
-
rescue => e
|
|
214
|
+
rescue StandardError => e
|
|
226
215
|
raise UploadError, "Bundle upload failed: #{e.message}"
|
|
227
216
|
end
|
|
228
217
|
end
|
|
229
218
|
|
|
230
219
|
def assign_to_track(edit_id, track, version_code, release_notes: nil, user_fraction: nil)
|
|
231
|
-
unless VALID_TRACKS.include?(track)
|
|
232
|
-
raise TrackError, "Invalid track '#{track}'. Valid tracks: #{VALID_TRACKS.join(', ')}"
|
|
233
|
-
end
|
|
220
|
+
raise TrackError, "Invalid track '#{track}'. Valid tracks: #{VALID_TRACKS.join(', ')}" unless VALID_TRACKS.include?(track)
|
|
234
221
|
|
|
235
222
|
puts "š Assigning to #{track} track..."
|
|
236
223
|
|
|
@@ -241,7 +228,7 @@ module Mysigner
|
|
|
241
228
|
)
|
|
242
229
|
|
|
243
230
|
# Add release notes if provided
|
|
244
|
-
if release_notes
|
|
231
|
+
if release_notes&.any?
|
|
245
232
|
release.release_notes = release_notes.map do |lang, text|
|
|
246
233
|
Google::Apis::AndroidpublisherV3::LocalizedText.new(
|
|
247
234
|
language: lang,
|
|
@@ -251,9 +238,7 @@ module Mysigner
|
|
|
251
238
|
end
|
|
252
239
|
|
|
253
240
|
# Add user fraction for staged rollouts
|
|
254
|
-
if user_fraction
|
|
255
|
-
release.user_fraction = user_fraction
|
|
256
|
-
end
|
|
241
|
+
release.user_fraction = user_fraction if user_fraction
|
|
257
242
|
|
|
258
243
|
# Build track update
|
|
259
244
|
track_obj = Google::Apis::AndroidpublisherV3::Track.new(
|
|
@@ -265,103 +250,101 @@ module Mysigner
|
|
|
265
250
|
end
|
|
266
251
|
|
|
267
252
|
def commit_edit(edit_id)
|
|
268
|
-
puts
|
|
253
|
+
puts 'š¾ Committing changes...'
|
|
269
254
|
begin
|
|
270
255
|
# Try with changesNotSentForReview first (for apps without managed review)
|
|
271
256
|
@service.commit_edit(@package_name, edit_id, changes_not_sent_for_review: true)
|
|
272
257
|
rescue Google::Apis::ClientError => e
|
|
273
258
|
error_text = e.message.to_s
|
|
274
259
|
# Also check body if present
|
|
275
|
-
if e.respond_to?(:body) && e.body
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
@service.commit_edit(@package_name, edit_id)
|
|
282
|
-
else
|
|
283
|
-
raise
|
|
284
|
-
end
|
|
260
|
+
error_text += " #{e.body}" if e.respond_to?(:body) && e.body
|
|
261
|
+
|
|
262
|
+
raise unless error_text.include?('changesNotSentForReview')
|
|
263
|
+
|
|
264
|
+
# App has managed review enabled, commit without the flag
|
|
265
|
+
@service.commit_edit(@package_name, edit_id)
|
|
285
266
|
end
|
|
286
267
|
end
|
|
287
268
|
|
|
288
269
|
def parse_google_error(error)
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
message = body&.dig('error', 'message') || error.message
|
|
296
|
-
details = body&.dig('error', 'errors')&.map { |e| e['message'] }&.join('; ')
|
|
297
|
-
full_message = details ? "#{message} (#{details})" : message
|
|
298
|
-
|
|
299
|
-
# Provide helpful context for common errors
|
|
300
|
-
case full_message.to_s.downcase
|
|
301
|
-
when /package.*not found/i, /app not found/i
|
|
302
|
-
"#{full_message}\n\nš” Make sure the package name '#{@package_name}' matches your app in Google Play Console."
|
|
303
|
-
when /not authorized/i, /permission denied/i, /forbidden/i
|
|
304
|
-
"#{full_message}\n\nš” Check that your service account has Editor or Admin access to the app in Google Play Console."
|
|
305
|
-
when /version.*code.*already/i, /already.*used/i
|
|
306
|
-
"#{full_message}\n\nš” Version code already exists. Increment versionCode in android/app/build.gradle and rebuild."
|
|
307
|
-
when /precondition.*check.*failed/i, /precondition.*failed/i
|
|
308
|
-
track_name = @current_track || "this track"
|
|
309
|
-
"#{full_message}\n\n" \
|
|
310
|
-
"š” Google Play Console requires setup before publishing to #{track_name}:\n\n" \
|
|
311
|
-
" For PRODUCTION track:\n" \
|
|
312
|
-
" ⢠Complete store listing (description, screenshots, etc.)\n" \
|
|
313
|
-
" ⢠Set content rating\n" \
|
|
314
|
-
" ⢠Configure pricing & distribution\n\n" \
|
|
315
|
-
" For BETA/ALPHA tracks:\n" \
|
|
316
|
-
" ⢠Create a closed/open testing track in Play Console\n" \
|
|
317
|
-
" ⢠Add at least one tester email\n\n" \
|
|
318
|
-
" For INTERNAL track:\n" \
|
|
319
|
-
" ⢠Add internal testers in Play Console\n\n" \
|
|
320
|
-
" ā
Your AAB was uploaded successfully!\n" \
|
|
321
|
-
" ā Go to Play Console to complete track setup, then use:\n" \
|
|
322
|
-
" mysigner submit #{track_name} --platform android --version-code VERSION"
|
|
323
|
-
when /invalid request/i
|
|
324
|
-
"#{full_message}\n\nš” Common causes:\n" \
|
|
325
|
-
" ⢠Version code not found on Google Play (must upload first)\n" \
|
|
326
|
-
" ⢠App not created in Google Play Console\n" \
|
|
327
|
-
" ⢠Service account missing permissions"
|
|
328
|
-
when /signing/i, /signature/i
|
|
329
|
-
"#{full_message}\n\nš” The AAB may not be signed with the correct key. Check your keystore matches what's registered in Play Console."
|
|
330
|
-
else
|
|
331
|
-
full_message
|
|
270
|
+
body = nil
|
|
271
|
+
if error.respond_to?(:body) && error.body
|
|
272
|
+
body = begin
|
|
273
|
+
JSON.parse(error.body)
|
|
274
|
+
rescue StandardError
|
|
275
|
+
nil
|
|
332
276
|
end
|
|
333
|
-
rescue => parse_error
|
|
334
|
-
"#{error.message} (parsing error: #{parse_error.message})"
|
|
335
277
|
end
|
|
278
|
+
|
|
279
|
+
message = body&.dig('error', 'message') || error.message
|
|
280
|
+
details = body&.dig('error', 'errors')&.map { |e| e['message'] }&.join('; ')
|
|
281
|
+
full_message = details ? "#{message} (#{details})" : message
|
|
282
|
+
|
|
283
|
+
# Provide helpful context for common errors
|
|
284
|
+
case full_message.to_s.downcase
|
|
285
|
+
when /package.*not found/i, /app not found/i
|
|
286
|
+
"#{full_message}\n\nš” Make sure the package name '#{@package_name}' matches your app in Google Play Console."
|
|
287
|
+
when /not authorized/i, /permission denied/i, /forbidden/i
|
|
288
|
+
"#{full_message}\n\nš” Check that your service account has Editor or Admin access to the app in Google Play Console."
|
|
289
|
+
when /version.*code.*already/i, /already.*used/i
|
|
290
|
+
"#{full_message}\n\nš” Version code already exists. Increment versionCode in android/app/build.gradle and rebuild."
|
|
291
|
+
when /precondition.*check.*failed/i, /precondition.*failed/i
|
|
292
|
+
track_name = @current_track || 'this track'
|
|
293
|
+
"#{full_message}\n\n" \
|
|
294
|
+
"š” Google Play Console requires setup before publishing to #{track_name}:\n\n " \
|
|
295
|
+
"For PRODUCTION track:\n " \
|
|
296
|
+
"⢠Complete store listing (description, screenshots, etc.)\n " \
|
|
297
|
+
"⢠Set content rating\n " \
|
|
298
|
+
"⢠Configure pricing & distribution\n\n " \
|
|
299
|
+
"For BETA/ALPHA tracks:\n " \
|
|
300
|
+
"⢠Create a closed/open testing track in Play Console\n " \
|
|
301
|
+
"⢠Add at least one tester email\n\n " \
|
|
302
|
+
"For INTERNAL track:\n " \
|
|
303
|
+
"⢠Add internal testers in Play Console\n\n " \
|
|
304
|
+
"ā
Your AAB was uploaded successfully!\n " \
|
|
305
|
+
"ā Go to Play Console to complete track setup, then use:\n " \
|
|
306
|
+
"mysigner submit #{track_name} --platform android --version-code VERSION"
|
|
307
|
+
when /invalid request/i
|
|
308
|
+
"#{full_message}\n\nš” Common causes:\n " \
|
|
309
|
+
"⢠Version code not found on Google Play (must upload first)\n " \
|
|
310
|
+
"⢠App not created in Google Play Console\n " \
|
|
311
|
+
'⢠Service account missing permissions'
|
|
312
|
+
when /signing/i, /signature/i
|
|
313
|
+
"#{full_message}\n\nš” The AAB may not be signed with the correct key. Check your keystore matches what's registered in Play Console."
|
|
314
|
+
else
|
|
315
|
+
full_message
|
|
316
|
+
end
|
|
317
|
+
rescue StandardError => e
|
|
318
|
+
"#{error.message} (parsing error: #{e.message})"
|
|
336
319
|
end
|
|
337
320
|
|
|
338
321
|
def say_uploading(track)
|
|
339
|
-
puts "āļø Uploading to Google Play#{
|
|
340
|
-
puts
|
|
322
|
+
puts "āļø Uploading to Google Play#{" (#{track} track)" if track}..."
|
|
323
|
+
puts ''
|
|
341
324
|
puts "AAB: #{File.basename(@aab_path)}"
|
|
342
325
|
puts "Size: #{format_bytes(File.size(@aab_path))}"
|
|
343
326
|
puts "Package: #{@package_name}"
|
|
344
327
|
puts "Track: #{track || 'none (upload only)'}"
|
|
345
|
-
puts
|
|
328
|
+
puts ''
|
|
346
329
|
end
|
|
347
330
|
|
|
348
331
|
def say_upload_success(version_code)
|
|
349
332
|
puts "ā Bundle uploaded successfully (version code: #{version_code})"
|
|
350
|
-
puts
|
|
333
|
+
puts ''
|
|
351
334
|
end
|
|
352
335
|
|
|
353
336
|
def say_success(track, version_code)
|
|
354
|
-
puts
|
|
355
|
-
puts
|
|
356
|
-
puts
|
|
357
|
-
puts
|
|
358
|
-
puts
|
|
337
|
+
puts ''
|
|
338
|
+
puts '=' * 80
|
|
339
|
+
puts 'ā Upload complete!'
|
|
340
|
+
puts '=' * 80
|
|
341
|
+
puts ''
|
|
359
342
|
puts "š Your app is now on Google Play (#{track} track)"
|
|
360
|
-
puts
|
|
343
|
+
puts ''
|
|
361
344
|
puts "Version Code: #{version_code}"
|
|
362
345
|
puts "Package: #{@package_name}"
|
|
363
346
|
puts "Track: #{track}"
|
|
364
|
-
puts
|
|
347
|
+
puts ''
|
|
365
348
|
end
|
|
366
349
|
|
|
367
350
|
def format_bytes(bytes)
|