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.
Files changed (47) 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/.rubocop.yml +55 -0
  6. data/.rubocop_todo.yml +112 -0
  7. data/CHANGELOG.md +96 -0
  8. data/Gemfile +5 -3
  9. data/Gemfile.lock +38 -8
  10. data/README.md +13 -15
  11. data/Rakefile +5 -3
  12. data/bin/console +4 -3
  13. data/bin/setup +3 -0
  14. data/exe/mysigner +2 -1
  15. data/lib/mysigner/build/android_executor.rb +46 -52
  16. data/lib/mysigner/build/android_parser.rb +33 -40
  17. data/lib/mysigner/build/configurator.rb +17 -16
  18. data/lib/mysigner/build/detector.rb +39 -50
  19. data/lib/mysigner/build/error_analyzer.rb +70 -68
  20. data/lib/mysigner/build/executor.rb +30 -37
  21. data/lib/mysigner/build/parser.rb +18 -18
  22. data/lib/mysigner/cli/auth_commands.rb +735 -752
  23. data/lib/mysigner/cli/build_commands.rb +697 -721
  24. data/lib/mysigner/cli/concerns/actionable_suggestions.rb +208 -154
  25. data/lib/mysigner/cli/concerns/api_helpers.rb +46 -54
  26. data/lib/mysigner/cli/concerns/error_handlers.rb +247 -237
  27. data/lib/mysigner/cli/concerns/helpers.rb +12 -1
  28. data/lib/mysigner/cli/diagnostic_commands.rb +659 -635
  29. data/lib/mysigner/cli/resource_commands.rb +880 -902
  30. data/lib/mysigner/cli/validate_commands.rb +25 -25
  31. data/lib/mysigner/cli.rb +3 -1
  32. data/lib/mysigner/client.rb +27 -19
  33. data/lib/mysigner/config.rb +93 -56
  34. data/lib/mysigner/export/exporter.rb +32 -36
  35. data/lib/mysigner/signing/certificate_checker.rb +18 -23
  36. data/lib/mysigner/signing/keystore_manager.rb +34 -39
  37. data/lib/mysigner/signing/validator.rb +38 -40
  38. data/lib/mysigner/signing/wizard.rb +329 -342
  39. data/lib/mysigner/upload/app_store_automation.rb +51 -49
  40. data/lib/mysigner/upload/app_store_submission.rb +87 -92
  41. data/lib/mysigner/upload/play_store_uploader.rb +98 -115
  42. data/lib/mysigner/upload/uploader.rb +101 -109
  43. data/lib/mysigner/version.rb +3 -1
  44. data/lib/mysigner.rb +13 -11
  45. data/mysigner.gemspec +36 -33
  46. data/test_manual.rb +37 -36
  47. metadata +37 -16
@@ -1,3 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'English'
1
4
  require 'fileutils'
2
5
  require 'tempfile'
3
6
 
@@ -20,7 +23,8 @@ module Mysigner
20
23
  # - key_alias: Key alias in keystore
21
24
  # - key_password: Key password (defaults to keystore_password)
22
25
  # - version_code: Override version code (passed via gradle property)
23
- def build_aab!(variant: 'release', keystore_path: nil, keystore_password: nil, key_alias: nil, key_password: nil, version_code: nil)
26
+ def build_aab!(variant: 'release', keystore_path: nil, keystore_password: nil, key_alias: nil, key_password: nil,
27
+ version_code: nil)
24
28
  @variant = variant
25
29
  @keystore_path = keystore_path
26
30
  @keystore_password = keystore_password
@@ -34,13 +38,11 @@ module Mysigner
34
38
  # Build
35
39
  success = run_gradle_build(task)
36
40
 
37
- unless success
38
- raise BuildError, "Android build failed. Check output above for errors."
39
- end
41
+ raise BuildError, 'Android build failed. Check output above for errors.' unless success
40
42
 
41
43
  # Find output AAB
42
44
  aab_path = find_aab_output(variant)
43
-
45
+
44
46
  unless aab_path && File.exist?(aab_path)
45
47
  raise BuildError, "Build reported success but AAB not found. Expected at: #{@parser.aab_output_path(variant)}"
46
48
  end
@@ -63,13 +65,11 @@ module Mysigner
63
65
  # Build
64
66
  success = run_gradle_build(task)
65
67
 
66
- unless success
67
- raise BuildError, "Android build failed. Check output above for errors."
68
- end
68
+ raise BuildError, 'Android build failed. Check output above for errors.' unless success
69
69
 
70
70
  # Find output APK
71
71
  apk_path = find_apk_output(variant)
72
-
72
+
73
73
  unless apk_path && File.exist?(apk_path)
74
74
  raise BuildError, "Build reported success but APK not found. Expected at: #{@parser.apk_output_path(variant)}"
75
75
  end
@@ -99,7 +99,7 @@ module Mysigner
99
99
  end
100
100
 
101
101
  def ensure_java_home!
102
- java_home = ENV['JAVA_HOME']
102
+ java_home = ENV.fetch('JAVA_HOME', nil)
103
103
 
104
104
  # Check if JAVA_HOME is set and valid
105
105
  if java_home && !java_home.empty? && Dir.exist?(java_home)
@@ -112,8 +112,8 @@ module Mysigner
112
112
  ENV['JAVA_HOME'] = detected
113
113
  elsif java_home && !java_home.empty?
114
114
  raise BuildError, "JAVA_HOME is set to invalid directory: #{java_home}\n" \
115
- "Run 'mysigner doctor' to fix, or set JAVA_HOME manually:\n" \
116
- " export JAVA_HOME=$(/usr/libexec/java_home -v 17)"
115
+ "Run 'mysigner doctor' to fix, or set JAVA_HOME manually:\n " \
116
+ 'export JAVA_HOME=$(/usr/libexec/java_home -v 17)'
117
117
  end
118
118
  end
119
119
 
@@ -122,12 +122,10 @@ module Mysigner
122
122
  end
123
123
 
124
124
  def ensure_android_home!
125
- android_home = ENV['ANDROID_HOME'] || ENV['ANDROID_SDK_ROOT']
125
+ android_home = ENV['ANDROID_HOME'] || ENV.fetch('ANDROID_SDK_ROOT', nil)
126
126
 
127
127
  # Check if already valid
128
- if android_home && !android_home.empty? && Dir.exist?(android_home)
129
- return
130
- end
128
+ return if android_home && !android_home.empty? && Dir.exist?(android_home)
131
129
 
132
130
  # Try to detect Android SDK
133
131
  detected = detect_android_home
@@ -137,8 +135,8 @@ module Mysigner
137
135
  ENV['ANDROID_SDK_ROOT'] = detected
138
136
  else
139
137
  raise BuildError, "Android SDK not found.\n" \
140
- "Run 'mysigner doctor' to diagnose, or set ANDROID_HOME:\n" \
141
- " export ANDROID_HOME=~/Library/Android/sdk"
138
+ "Run 'mysigner doctor' to diagnose, or set ANDROID_HOME:\n " \
139
+ 'export ANDROID_HOME=~/Library/Android/sdk'
142
140
  end
143
141
  end
144
142
 
@@ -190,7 +188,7 @@ module Mysigner
190
188
  case @project_info[:framework]
191
189
  when :capacitor
192
190
  # Capacitor: sync before build
193
- puts "🔄 Syncing Capacitor..."
191
+ puts '🔄 Syncing Capacitor...'
194
192
  Dir.chdir(@project_info[:directory]) do
195
193
  system('npx cap sync android > /dev/null 2>&1')
196
194
  end
@@ -199,14 +197,14 @@ module Mysigner
199
197
  if File.exist?(File.join(@project_info[:directory], 'node_modules'))
200
198
  # Dependencies already installed
201
199
  else
202
- puts "📦 Installing npm dependencies..."
200
+ puts '📦 Installing npm dependencies...'
203
201
  Dir.chdir(@project_info[:directory]) do
204
202
  system('npm install > /dev/null 2>&1') || system('yarn install > /dev/null 2>&1')
205
203
  end
206
204
  end
207
205
  when :flutter
208
206
  # Flutter: ensure dependencies are fetched
209
- puts "📦 Getting Flutter dependencies..."
207
+ puts '📦 Getting Flutter dependencies...'
210
208
  Dir.chdir(@project_info[:directory]) do
211
209
  system('flutter pub get > /dev/null 2>&1')
212
210
  end
@@ -218,26 +216,26 @@ module Mysigner
218
216
  gradle_cmd = @parser.gradle_command
219
217
 
220
218
  cmd_parts = []
221
-
219
+
222
220
  # Export JAVA_HOME if we detected/fixed it
223
- java_home = ENV['JAVA_HOME']
221
+ java_home = ENV.fetch('JAVA_HOME', nil)
224
222
  if java_home && Dir.exist?(java_home)
225
223
  cmd_parts << "export JAVA_HOME=#{shell_escape(java_home)}"
226
- cmd_parts << "&&"
224
+ cmd_parts << '&&'
227
225
  end
228
226
 
229
227
  # Export ANDROID_HOME if we detected/fixed it
230
- android_home = ENV['ANDROID_HOME']
228
+ android_home = ENV.fetch('ANDROID_HOME', nil)
231
229
  if android_home && Dir.exist?(android_home)
232
230
  cmd_parts << "export ANDROID_HOME=#{shell_escape(android_home)}"
233
- cmd_parts << "&&"
231
+ cmd_parts << '&&'
234
232
  cmd_parts << "export ANDROID_SDK_ROOT=#{shell_escape(android_home)}"
235
- cmd_parts << "&&"
233
+ cmd_parts << '&&'
236
234
  end
237
-
235
+
238
236
  # Change to android directory and run gradle
239
237
  cmd_parts << "cd #{shell_escape(android_dir)}"
240
- cmd_parts << "&&"
238
+ cmd_parts << '&&'
241
239
  cmd_parts << gradle_cmd
242
240
  cmd_parts << task
243
241
 
@@ -248,15 +246,13 @@ module Mysigner
248
246
  cmd_parts << "-Pandroid.injected.signing.key.alias=#{shell_escape(@key_alias)}" if @key_alias
249
247
  cmd_parts << "-Pandroid.injected.signing.key.password=#{shell_escape(@key_password)}" if @key_password
250
248
  end
251
-
249
+
252
250
  # Add version code override if provided (no file modification needed)
253
- if @version_code
254
- cmd_parts << "-PversionCode=#{@version_code}"
255
- end
251
+ cmd_parts << "-PversionCode=#{@version_code}" if @version_code
256
252
 
257
253
  # Standard build options
258
- cmd_parts << "--no-daemon" # Avoid daemon issues in CI
259
- cmd_parts << "-q" # Quiet mode (less noise)
254
+ cmd_parts << '--no-daemon' # Avoid daemon issues in CI
255
+ cmd_parts << '-q' # Quiet mode (less noise)
260
256
 
261
257
  cmd_parts.join(' ')
262
258
  end
@@ -266,29 +262,29 @@ module Mysigner
266
262
  gradle_cmd = @parser.gradle_command
267
263
 
268
264
  exports = []
269
- java_home = ENV['JAVA_HOME']
265
+ java_home = ENV.fetch('JAVA_HOME', nil)
270
266
  exports << "export JAVA_HOME=#{shell_escape(java_home)}" if java_home && Dir.exist?(java_home)
271
-
272
- android_home = ENV['ANDROID_HOME']
267
+
268
+ android_home = ENV.fetch('ANDROID_HOME', nil)
273
269
  if android_home && Dir.exist?(android_home)
274
270
  exports << "export ANDROID_HOME=#{shell_escape(android_home)}"
275
271
  exports << "export ANDROID_SDK_ROOT=#{shell_escape(android_home)}"
276
272
  end
277
-
278
- export_str = exports.any? ? exports.join(' && ') + ' && ' : ''
273
+
274
+ export_str = exports.any? ? "#{exports.join(' && ')} && " : ''
279
275
  cmd = "#{export_str}cd #{shell_escape(android_dir)} && #{gradle_cmd} #{task} --no-daemon -q"
280
276
  system(cmd)
281
277
  end
282
278
 
283
279
  def execute_with_output(cmd)
284
280
  puts "🏗️ Running: gradle #{@variant}..."
285
- puts ""
281
+ puts ''
286
282
 
287
283
  # Run command and capture output in real-time
288
284
  IO.popen("#{cmd} 2>&1", 'r') do |io|
289
285
  io.each_line do |line|
290
286
  next if line.strip.empty?
291
-
287
+
292
288
  # Show errors and warnings
293
289
  if line.include?('FAILURE') || line.include?('ERROR') || line.include?('error:')
294
290
  puts line
@@ -304,14 +300,14 @@ module Mysigner
304
300
  end
305
301
  end
306
302
 
307
- puts "" # New line after dots
303
+ puts '' # New line after dots
308
304
 
309
- $?.success?
305
+ $CHILD_STATUS.success?
310
306
  end
311
307
 
312
308
  def find_aab_output(variant)
313
309
  android_dir = @parser.android_directory
314
-
310
+
315
311
  # Search patterns for AAB files
316
312
  patterns = [
317
313
  File.join(android_dir, "app/build/outputs/bundle/#{variant}/*.aab"),
@@ -332,7 +328,7 @@ module Mysigner
332
328
 
333
329
  def find_apk_output(variant)
334
330
  android_dir = @parser.android_directory
335
-
331
+
336
332
  # Search patterns for APK files
337
333
  patterns = [
338
334
  File.join(android_dir, "app/build/outputs/apk/#{variant}/*.apk"),
@@ -353,14 +349,12 @@ module Mysigner
353
349
 
354
350
  def shell_escape(str)
355
351
  return "''" if str.nil? || str.empty?
356
-
352
+
357
353
  # If string contains no special characters, return as-is
358
- if str =~ /\A[a-zA-Z0-9_.\-\/]+\z/
359
- return str
360
- end
361
-
354
+ return str if str =~ %r{\A[a-zA-Z0-9_.\-/]+\z}
355
+
362
356
  # Otherwise, quote it
363
- "'" + str.gsub("'", "'\\''") + "'"
357
+ "'#{str.gsub("'", "'\\''")}'"
364
358
  end
365
359
  end
366
360
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Mysigner
2
4
  module Build
3
5
  class AndroidParser
@@ -57,42 +59,41 @@ module Mysigner
57
59
  # Get all build types (debug, release, etc.)
58
60
  def build_types
59
61
  types = []
60
-
62
+
61
63
  # Match buildTypes block
62
64
  if @gradle_content =~ /buildTypes\s*\{(.*?)\n\s*\}/m
63
- block = $1
65
+ block = ::Regexp.last_match(1)
64
66
  # Find all type names (e.g., "release {" or "debug {")
65
67
  block.scan(/(\w+)\s*\{/) do |match|
66
68
  types << match[0] unless %w[debug release].include?(match[0]) && types.include?(match[0])
67
69
  types << match[0]
68
70
  end
69
71
  end
70
-
72
+
71
73
  # Default build types if none found
72
- types = ['debug', 'release'] if types.empty?
74
+ types = %w[debug release] if types.empty?
73
75
  types.uniq
74
76
  end
75
77
 
76
78
  # Get all product flavors
77
79
  def product_flavors
78
80
  flavors = []
79
-
81
+
80
82
  if @gradle_content =~ /productFlavors\s*\{(.*?)\n\s{4}\}/m
81
- block = $1
83
+ block = ::Regexp.last_match(1)
82
84
  block.scan(/(\w+)\s*\{/) do |match|
83
85
  flavors << match[0]
84
86
  end
85
87
  end
86
-
88
+
87
89
  flavors
88
90
  end
89
91
 
90
92
  # Get signing config for a build type
91
93
  def signing_config(build_type = 'release')
92
94
  # Look for signingConfig in the build type block
93
- if @gradle_content =~ /#{build_type}\s*\{[^}]*signingConfig\s*(?:=\s*)?signingConfigs\.(\w+)/m
94
- return $1
95
- end
95
+ return ::Regexp.last_match(1) if @gradle_content =~ /#{build_type}\s*\{[^}]*signingConfig\s*(?:=\s*)?signingConfigs\.(\w+)/m
96
+
96
97
  nil
97
98
  end
98
99
 
@@ -104,30 +105,28 @@ module Mysigner
104
105
  # Get signing configs defined in the project
105
106
  def signing_configs
106
107
  configs = []
107
-
108
+
108
109
  if @gradle_content =~ /signingConfigs\s*\{(.*?)\n\s{4}\}/m
109
- block = $1
110
+ block = ::Regexp.last_match(1)
110
111
  block.scan(/(\w+)\s*\{/) do |match|
111
112
  configs << match[0]
112
113
  end
113
114
  end
114
-
115
+
115
116
  configs
116
117
  end
117
118
 
118
119
  # Get keystore path from signing config
119
120
  def keystore_path(config_name = 'release')
120
- if @gradle_content =~ /#{config_name}\s*\{[^}]*storeFile\s*(?:=\s*)?(?:file\()?"?([^")\n]+)"?\)?/m
121
- return $1
122
- end
121
+ return ::Regexp.last_match(1) if @gradle_content =~ /#{config_name}\s*\{[^}]*storeFile\s*(?:=\s*)?(?:file\()?"?([^")\n]+)"?\)?/m
122
+
123
123
  nil
124
124
  end
125
125
 
126
126
  # Get keystore alias from signing config
127
127
  def keystore_alias(config_name = 'release')
128
- if @gradle_content =~ /#{config_name}\s*\{[^}]*keyAlias\s*(?:=\s*)?["']?([^"'\n]+)["']?/m
129
- return $1.strip
130
- end
128
+ return ::Regexp.last_match(1).strip if @gradle_content =~ /#{config_name}\s*\{[^}]*keyAlias\s*(?:=\s*)?["']?([^"'\n]+)["']?/m
129
+
131
130
  nil
132
131
  end
133
132
 
@@ -137,15 +136,11 @@ module Mysigner
137
136
  strings_path = File.join(android_directory, 'app/src/main/res/values/strings.xml')
138
137
  if File.exist?(strings_path)
139
138
  content = File.read(strings_path)
140
- if content =~ /<string\s+name="app_name"[^>]*>([^<]+)<\/string>/
141
- return $1
142
- end
139
+ return ::Regexp.last_match(1) if content =~ %r{<string\s+name="app_name"[^>]*>([^<]+)</string>}
143
140
  end
144
141
 
145
142
  # Fallback to manifest label
146
- if @manifest_content && @manifest_content =~ /android:label="([^"]+)"/
147
- return $1
148
- end
143
+ return ::Regexp.last_match(1) if @manifest_content && @manifest_content =~ /android:label="([^"]+)"/
149
144
 
150
145
  # Fallback to directory name
151
146
  File.basename(@project_info[:directory])
@@ -217,7 +212,7 @@ module Mysigner
217
212
 
218
213
  def read_gradle_file
219
214
  gradle_path = @project_info[:app_build_gradle]
220
-
215
+
221
216
  unless gradle_path && File.exist?(gradle_path)
222
217
  # Try to find it
223
218
  android_dir = android_directory
@@ -228,12 +223,14 @@ module Mysigner
228
223
  end
229
224
 
230
225
  return '' unless File.exist?(gradle_path)
226
+
231
227
  File.read(gradle_path)
232
228
  end
233
229
 
234
230
  def read_manifest_file
235
231
  manifest_path = File.join(android_directory, 'app/src/main/AndroidManifest.xml')
236
232
  return nil unless File.exist?(manifest_path)
233
+
237
234
  File.read(manifest_path)
238
235
  end
239
236
 
@@ -241,7 +238,7 @@ module Mysigner
241
238
  # Handle both Groovy and Kotlin DSL syntax
242
239
  # Groovy: applicationId "com.example.app" or applicationId = "com.example.app"
243
240
  # Kotlin: applicationId = "com.example.app"
244
-
241
+
245
242
  patterns = [
246
243
  /#{property}\s*=?\s*["']([^"']+)["']/,
247
244
  /#{property}\s+["']([^"']+)["']/,
@@ -250,9 +247,7 @@ module Mysigner
250
247
  ]
251
248
 
252
249
  patterns.each do |pattern|
253
- if @gradle_content =~ pattern
254
- return $1
255
- end
250
+ return ::Regexp.last_match(1) if @gradle_content =~ pattern
256
251
  end
257
252
 
258
253
  nil
@@ -260,11 +255,9 @@ module Mysigner
260
255
 
261
256
  def extract_package_from_manifest
262
257
  return nil unless @manifest_content
263
-
264
- if @manifest_content =~ /package="([^"]+)"/
265
- return $1
266
- end
267
-
258
+
259
+ return ::Regexp.last_match(1) if @manifest_content =~ /package="([^"]+)"/
260
+
268
261
  nil
269
262
  end
270
263
 
@@ -272,19 +265,19 @@ module Mysigner
272
265
  # Check for app.json in project root
273
266
  project_dir = @project_info[:directory]
274
267
  app_json_path = File.join(project_dir, 'app.json')
275
-
268
+
276
269
  return nil unless File.exist?(app_json_path)
277
-
270
+
278
271
  begin
279
272
  require 'json'
280
273
  config = JSON.parse(File.read(app_json_path))
281
-
274
+
282
275
  # Expo config can be nested under 'expo' key or at root
283
276
  expo_config = config['expo'] || config
284
-
277
+
285
278
  # Get Android package name
286
279
  expo_config.dig('android', 'package')
287
- rescue
280
+ rescue StandardError
288
281
  nil
289
282
  end
290
283
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Mysigner
2
4
  module Build
3
5
  class Configurator
@@ -15,7 +17,7 @@ module Mysigner
15
17
  target = @parser.find_target(target_name)
16
18
  bundle_id = @parser.bundle_id(target_name, configuration)
17
19
 
18
- raise "Bundle ID not found in project" if bundle_id.to_s.empty?
20
+ raise 'Bundle ID not found in project' if bundle_id.to_s.empty?
19
21
 
20
22
  # Map build type to profile type
21
23
  profile_type = map_build_type_to_profile_type(build_type)
@@ -30,21 +32,21 @@ module Mysigner
30
32
  # Set manual signing
31
33
  config.build_settings['CODE_SIGN_STYLE'] = 'Manual'
32
34
  config.build_settings['PROVISIONING_PROFILE_SPECIFIER'] = profile['name']
33
-
35
+
34
36
  # Set code sign identity based on type
35
37
  code_sign_identity = case build_type
36
- when :development
37
- 'iPhone Developer'
38
- else
39
- 'iPhone Distribution'
40
- end
38
+ when :development
39
+ 'iPhone Developer'
40
+ else
41
+ 'iPhone Distribution'
42
+ end
41
43
  config.build_settings['CODE_SIGN_IDENTITY'] = code_sign_identity
42
44
 
43
45
  # Ensure development team is set
44
46
  team_id = @parser.team_id(target_name, configuration)
45
- if team_id.to_s.empty?
47
+ if team_id.to_s.empty? && profile['team_id']
46
48
  # Try to get from profile or use organization default
47
- config.build_settings['DEVELOPMENT_TEAM'] = profile['team_id'] if profile['team_id']
49
+ config.build_settings['DEVELOPMENT_TEAM'] = profile['team_id']
48
50
  end
49
51
 
50
52
  # Save project
@@ -93,9 +95,9 @@ module Mysigner
93
95
  type: profile_type
94
96
  }
95
97
  )
96
-
98
+
97
99
  return response[:profile] if response[:profile]
98
- rescue Mysigner::NotFoundError => e
100
+ rescue Mysigner::NotFoundError
99
101
  # Profile matching returned no match, fall through to manual search
100
102
  end
101
103
 
@@ -110,11 +112,11 @@ module Mysigner
110
112
  )
111
113
 
112
114
  profiles = response[:profiles] || []
113
-
115
+
114
116
  if profiles.empty?
115
- raise ProfileNotFoundError,
116
- "No active #{profile_type} profile found for bundle ID '#{bundle_id}'. " \
117
- "Create one at the My Signer dashboard or run 'mysigner profiles'"
117
+ raise ProfileNotFoundError,
118
+ "No active #{profile_type} profile found for bundle ID '#{bundle_id}'. " \
119
+ "Create one at the My Signer dashboard or run 'mysigner profiles'"
118
120
  end
119
121
 
120
122
  # Return the profile expiring furthest in the future
@@ -123,4 +125,3 @@ module Mysigner
123
125
  end
124
126
  end
125
127
  end
126
-