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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.githooks/pre-commit +15 -0
  3. data/.githooks/pre-push +21 -0
  4. data/.github/workflows/ci.yml +29 -0
  5. data/.gitignore +1 -0
  6. data/.rubocop.yml +55 -0
  7. data/.rubocop_todo.yml +112 -0
  8. data/CHANGELOG.md +96 -0
  9. data/Gemfile +5 -3
  10. data/Gemfile.lock +38 -8
  11. data/README.md +87 -17
  12. data/Rakefile +5 -3
  13. data/bin/console +4 -3
  14. data/bin/setup +3 -0
  15. data/exe/mysigner +2 -1
  16. data/lib/mysigner/build/android_executor.rb +46 -52
  17. data/lib/mysigner/build/android_parser.rb +33 -40
  18. data/lib/mysigner/build/configurator.rb +17 -16
  19. data/lib/mysigner/build/detector.rb +39 -50
  20. data/lib/mysigner/build/error_analyzer.rb +70 -68
  21. data/lib/mysigner/build/executor.rb +30 -37
  22. data/lib/mysigner/build/parser.rb +18 -18
  23. data/lib/mysigner/cli/auth_commands.rb +735 -752
  24. data/lib/mysigner/cli/build_commands.rb +697 -721
  25. data/lib/mysigner/cli/concerns/actionable_suggestions.rb +208 -154
  26. data/lib/mysigner/cli/concerns/api_helpers.rb +46 -54
  27. data/lib/mysigner/cli/concerns/error_handlers.rb +247 -237
  28. data/lib/mysigner/cli/concerns/helpers.rb +12 -1
  29. data/lib/mysigner/cli/diagnostic_commands.rb +659 -635
  30. data/lib/mysigner/cli/resource_commands.rb +1266 -822
  31. data/lib/mysigner/cli/validate_commands.rb +161 -0
  32. data/lib/mysigner/cli.rb +5 -1
  33. data/lib/mysigner/client.rb +27 -19
  34. data/lib/mysigner/config.rb +93 -56
  35. data/lib/mysigner/export/exporter.rb +32 -36
  36. data/lib/mysigner/signing/certificate_checker.rb +18 -23
  37. data/lib/mysigner/signing/keystore_manager.rb +34 -39
  38. data/lib/mysigner/signing/validator.rb +38 -40
  39. data/lib/mysigner/signing/wizard.rb +329 -342
  40. data/lib/mysigner/upload/app_store_automation.rb +51 -49
  41. data/lib/mysigner/upload/app_store_submission.rb +87 -92
  42. data/lib/mysigner/upload/play_store_uploader.rb +98 -115
  43. data/lib/mysigner/upload/uploader.rb +101 -109
  44. data/lib/mysigner/version.rb +3 -1
  45. data/lib/mysigner.rb +13 -11
  46. data/mysigner.gemspec +36 -33
  47. data/test_manual.rb +37 -36
  48. metadata +38 -16
@@ -1,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'English'
1
4
  require 'fileutils'
2
5
  require 'json'
3
6
 
@@ -11,7 +14,7 @@ module Mysigner
11
14
  TRANSPORTER_PATHS = [
12
15
  '/Applications/Xcode.app/Contents/Developer/usr/bin/iTMSTransporter', # Bundled with Xcode (primary)
13
16
  '/Applications/Transporter.app/Contents/itms/bin/iTMSTransporter', # Standalone Transporter app
14
- '/usr/local/itms/bin/iTMSTransporter' # Custom installation
17
+ '/usr/local/itms/bin/iTMSTransporter' # Custom installation
15
18
  ].freeze
16
19
 
17
20
  def initialize(ipa_path, api_key:, api_issuer:, private_key:)
@@ -19,7 +22,7 @@ module Mysigner
19
22
  @api_key = api_key
20
23
  @api_issuer = api_issuer
21
24
  @private_key = private_key
22
-
25
+
23
26
  validate_ipa!
24
27
  detect_transporter!
25
28
  setup_private_key!
@@ -27,35 +30,31 @@ module Mysigner
27
30
 
28
31
  def upload!(wait_for_processing: false)
29
32
  say_uploading
30
-
33
+
31
34
  begin
32
35
  # Validate IPA first
33
36
  validate_result = validate_ipa
34
- unless validate_result[:success]
35
- raise UploadError, "IPA validation failed: #{validate_result[:error]}"
36
- end
37
-
37
+ raise UploadError, "IPA validation failed: #{validate_result[:error]}" unless validate_result[:success]
38
+
38
39
  # Upload to App Store Connect
39
40
  upload_result = upload_ipa
40
- unless upload_result[:success]
41
- raise UploadError, "Upload failed: #{upload_result[:error]}"
42
- end
43
-
41
+ raise UploadError, "Upload failed: #{upload_result[:error]}" unless upload_result[:success]
42
+
44
43
  say_success
45
-
44
+
46
45
  # Optionally wait for processing
47
46
  if wait_for_processing
48
47
  say_waiting_for_processing
49
- # Note: Build processing status is polled via the dashboard's sync API
48
+ # NOTE: Build processing status is polled via the dashboard's sync API
50
49
  # in build_commands.rb, not directly here. This flag is reserved for
51
50
  # future use or manual uploads outside the `ship` command flow.
52
51
  end
53
-
52
+
54
53
  {
55
54
  success: true,
56
- message: "Upload completed successfully"
55
+ message: 'Upload completed successfully'
57
56
  }
58
- rescue => e
57
+ rescue StandardError => e
59
58
  raise UploadError, "Upload failed: #{e.message}"
60
59
  end
61
60
  end
@@ -63,25 +62,21 @@ module Mysigner
63
62
  private
64
63
 
65
64
  def validate_ipa!
66
- unless File.exist?(@ipa_path)
67
- raise UploadError, "IPA file not found: #{@ipa_path}"
68
- end
69
-
70
- unless @ipa_path.end_with?('.ipa')
71
- raise UploadError, "Invalid file type: #{@ipa_path} (must be .ipa)"
72
- end
73
-
65
+ raise UploadError, "IPA file not found: #{@ipa_path}" unless File.exist?(@ipa_path)
66
+
67
+ raise UploadError, "Invalid file type: #{@ipa_path} (must be .ipa)" unless @ipa_path.end_with?('.ipa')
68
+
74
69
  # Check file size (should be at least 10KB to catch truly corrupt files)
75
70
  file_size = File.size(@ipa_path)
76
- if file_size < 10_000
77
- raise UploadError, "IPA file seems too small: #{file_size} bytes (possible corruption)"
78
- end
71
+ return unless file_size < 10_000
72
+
73
+ raise UploadError, "IPA file seems too small: #{file_size} bytes (possible corruption)"
79
74
  end
80
75
 
81
76
  def detect_transporter!
82
77
  # We primarily use xcrun altool, but check for iTMSTransporter as fallback
83
78
  @transporter_path = TRANSPORTER_PATHS.find { |path| File.exist?(path) }
84
-
79
+
85
80
  # Even if iTMSTransporter is not found, xcrun altool should work
86
81
  # This is just for informational purposes
87
82
  @using_altool = true
@@ -91,30 +86,30 @@ module Mysigner
91
86
  # iTMSTransporter looks for .p8 files in these locations:
92
87
  # - ./private_keys/AuthKey_<KEY_ID>.p8
93
88
  # - ~/private_keys/AuthKey_<KEY_ID>.p8
94
- # - ~/.private_keys/AuthKey_<KEY_ID>.p8
89
+ # - ~/.private_keys/AuthKey_<KEY_ID>.p8
95
90
  # - ~/.appstoreconnect/private_keys/AuthKey_<KEY_ID>.p8
96
-
97
- @private_keys_dir = File.expand_path("~/.private_keys")
91
+
92
+ @private_keys_dir = File.expand_path('~/.private_keys')
98
93
  FileUtils.mkdir_p(@private_keys_dir)
99
-
94
+
100
95
  @private_key_path = File.join(@private_keys_dir, "AuthKey_#{@api_key}.p8")
101
-
96
+
102
97
  # Write the private key to disk
103
98
  File.write(@private_key_path, @private_key)
104
- File.chmod(0600, @private_key_path) # Secure permissions
99
+ File.chmod(0o600, @private_key_path) # Secure permissions
105
100
  end
106
101
 
107
102
  def validate_ipa
108
- puts "📋 Validating IPA..."
109
- puts ""
110
-
103
+ puts '📋 Validating IPA...'
104
+ puts ''
105
+
111
106
  # Try altool first (simpler, works today)
112
107
  if altool_available?
113
108
  validate_with_altool
114
109
  elsif @transporter_path
115
110
  validate_with_transporter
116
111
  else
117
- puts "⚠️ Skipping validation (no tools available)"
112
+ puts '⚠️ Skipping validation (no tools available)'
118
113
  { success: true } # Continue anyway
119
114
  end
120
115
  end
@@ -128,12 +123,12 @@ module Mysigner
128
123
  '--apiKey', @api_key,
129
124
  '--apiIssuer', @api_issuer
130
125
  ].join(' ')
131
-
126
+
132
127
  output = `#{cmd} 2>&1`
133
- success = $?.success?
134
-
128
+ success = $CHILD_STATUS.success?
129
+
135
130
  if success
136
- puts "✓ Validation passed (altool)"
131
+ puts '✓ Validation passed (altool)'
137
132
  { success: true }
138
133
  else
139
134
  error_message = extract_error_from_output(output)
@@ -151,14 +146,14 @@ module Mysigner
151
146
  '-f', @ipa_path,
152
147
  '-apiKey', @api_key,
153
148
  '-apiIssuer', @api_issuer,
154
- '-t', 'Signiant' # Transport mode
149
+ '-t', 'Signiant' # Transport mode
155
150
  ].join(' ')
156
-
151
+
157
152
  output = `#{cmd} 2>&1`
158
- success = $?.success?
159
-
153
+ success = $CHILD_STATUS.success?
154
+
160
155
  if success
161
- puts "✓ Validation passed (iTMSTransporter)"
156
+ puts '✓ Validation passed (iTMSTransporter)'
162
157
  { success: true }
163
158
  else
164
159
  error_message = extract_error_from_output(output)
@@ -168,24 +163,24 @@ module Mysigner
168
163
  end
169
164
 
170
165
  def upload_ipa
171
- puts ""
172
- puts "☁️ Uploading to App Store Connect..."
173
- puts ""
174
-
166
+ puts ''
167
+ puts '☁️ Uploading to App Store Connect...'
168
+ puts ''
169
+
175
170
  # Try altool first, fall back to iTMSTransporter
176
171
  if altool_available?
177
172
  upload_with_altool
178
173
  elsif @transporter_path
179
174
  upload_with_transporter
180
175
  else
181
- raise UploadError, "No upload tool available. Please ensure Xcode is installed."
176
+ raise UploadError, 'No upload tool available. Please ensure Xcode is installed.'
182
177
  end
183
178
  end
184
179
 
185
180
  def upload_with_altool
186
- puts "Using: xcrun altool"
187
- puts ""
188
-
181
+ puts 'Using: xcrun altool'
182
+ puts ''
183
+
189
184
  cmd = [
190
185
  'xcrun', 'altool',
191
186
  '--upload-app',
@@ -194,40 +189,38 @@ module Mysigner
194
189
  '--apiKey', @api_key,
195
190
  '--apiIssuer', @api_issuer
196
191
  ].join(' ')
197
-
192
+
198
193
  # Capture full output for error detection
199
194
  output = []
200
195
  has_errors = false
201
-
196
+
202
197
  # Run with live output
203
- IO.popen(cmd, err: [:child, :out]) do |io|
198
+ IO.popen(cmd, err: %i[child out]) do |io|
204
199
  io.each_line do |line|
205
200
  output << line
206
201
  next if line.strip.empty?
207
-
202
+
208
203
  # Detect errors
209
- if line.include?('ERROR') || line.include?('UPLOAD FAILED')
210
- has_errors = true
211
- end
212
-
204
+ has_errors = true if line.include?('ERROR') || line.include?('UPLOAD FAILED')
205
+
213
206
  # Show progress indicators
214
- if line.include?('Uploading') || line.include?('Processing') ||
207
+ if line.include?('Uploading') || line.include?('Processing') ||
215
208
  line.include?('Verifying') || line.include?('Package')
216
209
  print '.'
217
210
  elsif line.include?('error') || line.include?('ERROR')
218
- puts ""
211
+ puts ''
219
212
  puts line
220
213
  elsif line.include?('SUCCESS') || line.include?('No errors')
221
- puts ""
214
+ puts ''
222
215
  puts line
223
216
  end
224
217
  end
225
218
  end
226
-
227
- puts "" # New line after progress dots
228
-
219
+
220
+ puts '' # New line after progress dots
221
+
229
222
  # Check both exit code and output for errors
230
- if $?.success? && !has_errors
223
+ if $CHILD_STATUS.success? && !has_errors
231
224
  { success: true }
232
225
  else
233
226
  error_msg = extract_error_from_output(output.join("\n"))
@@ -236,9 +229,9 @@ module Mysigner
236
229
  end
237
230
 
238
231
  def upload_with_transporter
239
- puts "Using: iTMSTransporter (future-proof)"
240
- puts ""
241
-
232
+ puts 'Using: iTMSTransporter (future-proof)'
233
+ puts ''
234
+
242
235
  # iTMSTransporter upload mode
243
236
  # According to https://help.apple.com/itc/transporteruserguide/en.lproj/static.html
244
237
  cmd = [
@@ -247,34 +240,34 @@ module Mysigner
247
240
  '-f', @ipa_path,
248
241
  '-apiKey', @api_key,
249
242
  '-apiIssuer', @api_issuer,
250
- '-t', 'Signiant' # Transport mode (can also use 'Aspera' or 'DAV')
243
+ '-t', 'Signiant' # Transport mode (can also use 'Aspera' or 'DAV')
251
244
  ].join(' ')
252
-
245
+
253
246
  # Capture output
254
247
  output = []
255
-
256
- IO.popen(cmd, err: [:child, :out]) do |io|
248
+
249
+ IO.popen(cmd, err: %i[child out]) do |io|
257
250
  io.each_line do |line|
258
251
  output << line
259
252
  next if line.strip.empty?
260
-
253
+
261
254
  # Show progress
262
- if line.include?('Uploading') || line.include?('Processing') ||
255
+ if line.include?('Uploading') || line.include?('Processing') ||
263
256
  line.include?('Verifying')
264
257
  print '.'
265
258
  elsif line.include?('ERROR')
266
- puts ""
259
+ puts ''
267
260
  puts line
268
261
  elsif line.include?('SUCCESS')
269
- puts ""
262
+ puts ''
270
263
  puts line
271
264
  end
272
265
  end
273
266
  end
274
-
275
- puts ""
276
-
277
- if $?.success?
267
+
268
+ puts ''
269
+
270
+ if $CHILD_STATUS.success?
278
271
  { success: true }
279
272
  else
280
273
  error_msg = extract_error_from_output(output.join("\n"))
@@ -292,13 +285,13 @@ module Mysigner
292
285
  error_lines = output.lines.select do |l|
293
286
  l.include?('ERROR') || l.include?('error') || l.include?('Invalid')
294
287
  end
295
-
288
+
296
289
  if error_lines.any?
297
290
  # Clean up and join error messages
298
291
  cleaned_errors = error_lines.map(&:strip)
299
- .reject { |l| l.empty? || l.start_with?('code :') || l.start_with?('iris-code') }
300
- .join("\n")
301
-
292
+ .reject { |l| l.empty? || l.start_with?('code :') || l.start_with?('iris-code') }
293
+ .join("\n")
294
+
302
295
  # Add helpful context for common errors
303
296
  if output.include?('Cannot determine the Apple ID from Bundle ID')
304
297
  cleaned_errors += "\n\n💡 This usually means:\n"
@@ -318,44 +311,44 @@ module Mysigner
318
311
  cleaned_errors += " 3. Add icon images for all required sizes\n"
319
312
  cleaned_errors += " 4. Or use a tool like https://appicon.co to generate all sizes\n"
320
313
  end
321
-
314
+
322
315
  cleaned_errors
323
316
  else
324
- "Unknown error"
317
+ 'Unknown error'
325
318
  end
326
319
  end
327
320
 
328
321
  def say_uploading
329
- puts "☁️ Uploading to TestFlight..."
330
- puts ""
322
+ puts '☁️ Uploading to TestFlight...'
323
+ puts ''
331
324
  puts "IPA: #{File.basename(@ipa_path)}"
332
325
  puts "Size: #{format_bytes(File.size(@ipa_path))}"
333
-
326
+
334
327
  # Show which tool will be used
335
328
  if altool_available?
336
- puts "Tool: xcrun altool"
329
+ puts 'Tool: xcrun altool'
337
330
  elsif @transporter_path
338
- puts "Tool: iTMSTransporter (future-proof)"
331
+ puts 'Tool: iTMSTransporter (future-proof)'
339
332
  else
340
- puts "Tool: Auto-detect"
333
+ puts 'Tool: Auto-detect'
341
334
  end
342
- puts ""
335
+ puts ''
343
336
  end
344
337
 
345
338
  def say_success
346
- puts ""
347
- puts "=" * 80
348
- puts "✓ Upload succeeded!"
349
- puts "=" * 80
350
- puts ""
351
- puts "🎉 Your build is now processing in App Store Connect"
352
- puts ""
339
+ puts ''
340
+ puts '=' * 80
341
+ puts '✓ Upload succeeded!'
342
+ puts '=' * 80
343
+ puts ''
344
+ puts '🎉 Your build is now processing in App Store Connect'
345
+ puts ''
353
346
  end
354
347
 
355
348
  def say_waiting_for_processing
356
- puts "⏳ Waiting for App Store Connect to process the build..."
357
- puts "This may take 5-15 minutes..."
358
- puts ""
349
+ puts '⏳ Waiting for App Store Connect to process the build...'
350
+ puts 'This may take 5-15 minutes...'
351
+ puts ''
359
352
  end
360
353
 
361
354
  def format_bytes(bytes)
@@ -370,4 +363,3 @@ module Mysigner
370
363
  end
371
364
  end
372
365
  end
373
-
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Mysigner
2
- VERSION = "0.1.1"
4
+ VERSION = '0.1.3'
3
5
  end
data/lib/mysigner.rb CHANGED
@@ -1,15 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Mysigner
2
4
  class Error < StandardError; end
3
5
  end
4
6
 
5
- require "mysigner/version"
6
- require "mysigner/config"
7
- require "mysigner/client"
8
- require "mysigner/build/detector"
9
- require "mysigner/build/parser"
10
- require "mysigner/build/configurator"
11
- require "mysigner/build/executor"
12
- require "mysigner/signing/validator"
13
- require "mysigner/export/exporter"
14
- require "mysigner/upload/uploader"
15
- require "mysigner/cli"
7
+ require 'mysigner/version'
8
+ require 'mysigner/config'
9
+ require 'mysigner/client'
10
+ require 'mysigner/build/detector'
11
+ require 'mysigner/build/parser'
12
+ require 'mysigner/build/configurator'
13
+ require 'mysigner/build/executor'
14
+ require 'mysigner/signing/validator'
15
+ require 'mysigner/export/exporter'
16
+ require 'mysigner/upload/uploader'
17
+ require 'mysigner/cli'
data/mysigner.gemspec CHANGED
@@ -1,32 +1,34 @@
1
+ # frozen_string_literal: true
1
2
 
2
- lib = File.expand_path("../lib", __FILE__)
3
+ lib = File.expand_path('lib', __dir__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require "mysigner/version"
5
+ require 'mysigner/version'
5
6
 
6
7
  Gem::Specification.new do |spec|
7
- spec.name = "mysigner"
8
+ spec.name = 'mysigner'
8
9
  spec.version = Mysigner::VERSION
9
- spec.authors = ["Jurgen Leka"]
10
- spec.email = ["lekacoding@gmail.com"]
10
+ spec.authors = ['Jurgen Leka']
11
+ spec.email = ['lekacoding@gmail.com']
11
12
 
12
- spec.summary = %q{CLI tool for iOS and Android code signing automation via My Signer API}
13
- spec.description = %q{Command-line interface for managing iOS certificates, devices, provisioning profiles, and Android keystores. Build, sign, and upload to App Store Connect and Google Play with simple commands like 'mysigner ship testflight' and 'mysigner ship internal --platform android'.}
14
- spec.homepage = "https://mysigner.dev"
15
- spec.license = "Apache-2.0"
13
+ spec.summary = 'CLI tool for iOS and Android code signing automation via My Signer API'
14
+ spec.description = "Command-line interface for managing iOS certificates, devices, provisioning profiles, and Android keystores. Build, sign, and upload to App Store Connect and Google Play with simple commands like 'mysigner ship testflight' and 'mysigner ship internal --platform android'."
15
+ spec.homepage = 'https://mysigner.dev'
16
+ spec.license = 'Apache-2.0'
16
17
 
17
- spec.required_ruby_version = ">= 3.2.0"
18
+ spec.required_ruby_version = '>= 3.2.0'
18
19
 
19
20
  # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
20
21
  # to allow pushing to a single host or delete this section to allow pushing to any host.
21
22
  if spec.respond_to?(:metadata)
22
- spec.metadata["allowed_push_host"] = "https://rubygems.org"
23
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
23
24
 
24
- spec.metadata["homepage_uri"] = spec.homepage
25
- spec.metadata["changelog_uri"] = "https://mysigner.dev/docs/changelog"
26
- spec.metadata["documentation_uri"] = "https://mysigner.dev/docs/commands"
25
+ spec.metadata['homepage_uri'] = spec.homepage
26
+ spec.metadata['changelog_uri'] = 'https://mysigner.dev/docs/changelog'
27
+ spec.metadata['documentation_uri'] = 'https://mysigner.dev/docs/commands'
28
+ spec.metadata['rubygems_mfa_required'] = 'true'
27
29
  else
28
- raise "RubyGems 2.0 or newer is required to protect against " \
29
- "public gem pushes."
30
+ raise 'RubyGems 2.0 or newer is required to protect against ' \
31
+ 'public gem pushes.'
30
32
  end
31
33
 
32
34
  spec.post_install_message = <<~MSG
@@ -50,29 +52,30 @@ Gem::Specification.new do |spec|
50
52
 
51
53
  # Specify which files should be added to the gem when it is released.
52
54
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
53
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
55
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
54
56
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
55
57
  end
56
- spec.bindir = "exe"
58
+ spec.bindir = 'exe'
57
59
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
58
- spec.require_paths = ["lib"]
60
+ spec.require_paths = ['lib']
59
61
 
60
62
  # Runtime dependencies
61
- spec.add_runtime_dependency "thor", "~> 1.4"
62
- spec.add_runtime_dependency "faraday", "~> 2.14"
63
- spec.add_runtime_dependency "faraday-retry", "~> 2.2"
64
- spec.add_runtime_dependency "reline", "~> 0.5"
65
- spec.add_runtime_dependency "base64", "~> 0.2"
66
- spec.add_runtime_dependency "xcodeproj", "~> 1.27"
67
- spec.add_runtime_dependency "plist", "~> 3.7"
68
-
63
+ spec.add_dependency 'base64', '~> 0.2'
64
+ spec.add_dependency 'faraday', '~> 2.14'
65
+ spec.add_dependency 'faraday-retry', '~> 2.2'
66
+ spec.add_dependency 'plist', '~> 3.7'
67
+ spec.add_dependency 'reline', '~> 0.5'
68
+ spec.add_dependency 'thor', '~> 1.4'
69
+ spec.add_dependency 'xcodeproj', '~> 1.27'
70
+
69
71
  # Android/Google Play dependencies
70
- spec.add_runtime_dependency "google-apis-androidpublisher_v3", "~> 0.54"
71
- spec.add_runtime_dependency "googleauth", "~> 1.11"
72
+ spec.add_dependency 'google-apis-androidpublisher_v3', '~> 0.54'
73
+ spec.add_dependency 'googleauth', '~> 1.11'
72
74
 
73
75
  # Development dependencies
74
- spec.add_development_dependency "bundler", "~> 2.5"
75
- spec.add_development_dependency "rake", "~> 13.0"
76
- spec.add_development_dependency "rspec", "~> 3.0"
77
- spec.add_development_dependency "webmock", "~> 3.24"
76
+ spec.add_development_dependency 'bundler', '~> 2.5'
77
+ spec.add_development_dependency 'rake', '~> 13.0'
78
+ spec.add_development_dependency 'rspec', '~> 3.0'
79
+ spec.add_development_dependency 'rubocop', '~> 1.79'
80
+ spec.add_development_dependency 'webmock', '~> 3.24'
78
81
  end