mysigner 0.1.2 → 0.1.4
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 +4 -0
- data/.rubocop.yml +55 -0
- data/.rubocop_todo.yml +126 -0
- data/CHANGELOG.md +96 -0
- data/Gemfile +5 -3
- data/Gemfile.lock +38 -8
- data/README.md +14 -16
- data/Rakefile +5 -3
- data/bin/console +4 -3
- data/bin/setup +3 -0
- data/certificate_.cer +0 -0
- data/exe/mysigner +19 -2
- data/iOS_App_Store_Profile.mobileprovision +1 -0
- data/iOS_Distribution_Certificate.cer +1 -0
- data/lib/mysigner/build/android_executor.rb +83 -63
- 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/cleanup/private_keys_purger.rb +41 -0
- data/lib/mysigner/cli/auth_commands.rb +771 -764
- data/lib/mysigner/cli/build_commands.rb +962 -796
- 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 +44 -1
- data/lib/mysigner/cli/diagnostic_commands.rb +667 -636
- data/lib/mysigner/cli/resource_commands.rb +1153 -985
- data/lib/mysigner/cli/validate_commands.rb +25 -25
- data/lib/mysigner/cli.rb +11 -1
- data/lib/mysigner/client.rb +27 -19
- data/lib/mysigner/config.rb +161 -60
- data/lib/mysigner/export/exporter.rb +38 -37
- data/lib/mysigner/signing/certificate_checker.rb +18 -23
- data/lib/mysigner/signing/gradle_signing_injector.rb +67 -0
- data/lib/mysigner/signing/keystore_manager.rb +81 -61
- data/lib/mysigner/signing/validator.rb +38 -40
- data/lib/mysigner/signing/wizard.rb +329 -342
- data/lib/mysigner/upload/app_store_automation.rb +96 -49
- data/lib/mysigner/upload/app_store_submission.rb +87 -92
- data/lib/mysigner/upload/asc_rest_uploader.rb +119 -0
- data/lib/mysigner/upload/play_store_uploader.rb +164 -144
- data/lib/mysigner/upload/uploader.rb +136 -115
- data/lib/mysigner/version.rb +3 -1
- data/lib/mysigner.rb +13 -11
- data/mysigner.gemspec +36 -33
- data/profile_.mobileprovision +0 -0
- data/test_manual.rb +37 -36
- metadata +44 -17
- data/.DS_Store +0 -0
|
@@ -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,
|
|
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
|
|
@@ -91,15 +91,27 @@ module Mysigner
|
|
|
91
91
|
# Handle framework-specific pre-build steps
|
|
92
92
|
run_pre_build_steps
|
|
93
93
|
|
|
94
|
-
#
|
|
95
|
-
|
|
94
|
+
# Phase 0: inject signing via Gradle init-script + env vars. Passwords
|
|
95
|
+
# never appear in argv (no -Pandroid.injected.signing.*=PLAINTEXT).
|
|
96
|
+
injector = nil
|
|
97
|
+
if @keystore_path && File.exist?(@keystore_path)
|
|
98
|
+
require 'mysigner/signing/gradle_signing_injector'
|
|
99
|
+
injector = Mysigner::Signing::GradleSigningInjector.new
|
|
100
|
+
@signing_init_script_path = injector.write_init_script!
|
|
101
|
+
end
|
|
96
102
|
|
|
97
|
-
|
|
98
|
-
|
|
103
|
+
begin
|
|
104
|
+
# Build command with signing properties referencing the init script
|
|
105
|
+
cmd = build_gradle_command(task)
|
|
106
|
+
execute_with_output(cmd)
|
|
107
|
+
ensure
|
|
108
|
+
injector&.cleanup!
|
|
109
|
+
@signing_init_script_path = nil
|
|
110
|
+
end
|
|
99
111
|
end
|
|
100
112
|
|
|
101
113
|
def ensure_java_home!
|
|
102
|
-
java_home = ENV
|
|
114
|
+
java_home = ENV.fetch('JAVA_HOME', nil)
|
|
103
115
|
|
|
104
116
|
# Check if JAVA_HOME is set and valid
|
|
105
117
|
if java_home && !java_home.empty? && Dir.exist?(java_home)
|
|
@@ -112,8 +124,8 @@ module Mysigner
|
|
|
112
124
|
ENV['JAVA_HOME'] = detected
|
|
113
125
|
elsif java_home && !java_home.empty?
|
|
114
126
|
raise BuildError, "JAVA_HOME is set to invalid directory: #{java_home}\n" \
|
|
115
|
-
|
|
116
|
-
|
|
127
|
+
"Run 'mysigner doctor' to fix, or set JAVA_HOME manually:\n " \
|
|
128
|
+
'export JAVA_HOME=$(/usr/libexec/java_home -v 17)'
|
|
117
129
|
end
|
|
118
130
|
end
|
|
119
131
|
|
|
@@ -122,12 +134,10 @@ module Mysigner
|
|
|
122
134
|
end
|
|
123
135
|
|
|
124
136
|
def ensure_android_home!
|
|
125
|
-
android_home = ENV['ANDROID_HOME'] || ENV
|
|
137
|
+
android_home = ENV['ANDROID_HOME'] || ENV.fetch('ANDROID_SDK_ROOT', nil)
|
|
126
138
|
|
|
127
139
|
# Check if already valid
|
|
128
|
-
if android_home && !android_home.empty? && Dir.exist?(android_home)
|
|
129
|
-
return
|
|
130
|
-
end
|
|
140
|
+
return if android_home && !android_home.empty? && Dir.exist?(android_home)
|
|
131
141
|
|
|
132
142
|
# Try to detect Android SDK
|
|
133
143
|
detected = detect_android_home
|
|
@@ -137,8 +147,8 @@ module Mysigner
|
|
|
137
147
|
ENV['ANDROID_SDK_ROOT'] = detected
|
|
138
148
|
else
|
|
139
149
|
raise BuildError, "Android SDK not found.\n" \
|
|
140
|
-
|
|
141
|
-
|
|
150
|
+
"Run 'mysigner doctor' to diagnose, or set ANDROID_HOME:\n " \
|
|
151
|
+
'export ANDROID_HOME=~/Library/Android/sdk'
|
|
142
152
|
end
|
|
143
153
|
end
|
|
144
154
|
|
|
@@ -190,7 +200,7 @@ module Mysigner
|
|
|
190
200
|
case @project_info[:framework]
|
|
191
201
|
when :capacitor
|
|
192
202
|
# Capacitor: sync before build
|
|
193
|
-
puts
|
|
203
|
+
puts '🔄 Syncing Capacitor...'
|
|
194
204
|
Dir.chdir(@project_info[:directory]) do
|
|
195
205
|
system('npx cap sync android > /dev/null 2>&1')
|
|
196
206
|
end
|
|
@@ -199,14 +209,14 @@ module Mysigner
|
|
|
199
209
|
if File.exist?(File.join(@project_info[:directory], 'node_modules'))
|
|
200
210
|
# Dependencies already installed
|
|
201
211
|
else
|
|
202
|
-
puts
|
|
212
|
+
puts '📦 Installing npm dependencies...'
|
|
203
213
|
Dir.chdir(@project_info[:directory]) do
|
|
204
214
|
system('npm install > /dev/null 2>&1') || system('yarn install > /dev/null 2>&1')
|
|
205
215
|
end
|
|
206
216
|
end
|
|
207
217
|
when :flutter
|
|
208
218
|
# Flutter: ensure dependencies are fetched
|
|
209
|
-
puts
|
|
219
|
+
puts '📦 Getting Flutter dependencies...'
|
|
210
220
|
Dir.chdir(@project_info[:directory]) do
|
|
211
221
|
system('flutter pub get > /dev/null 2>&1')
|
|
212
222
|
end
|
|
@@ -218,45 +228,57 @@ module Mysigner
|
|
|
218
228
|
gradle_cmd = @parser.gradle_command
|
|
219
229
|
|
|
220
230
|
cmd_parts = []
|
|
221
|
-
|
|
231
|
+
|
|
222
232
|
# Export JAVA_HOME if we detected/fixed it
|
|
223
|
-
java_home = ENV
|
|
233
|
+
java_home = ENV.fetch('JAVA_HOME', nil)
|
|
224
234
|
if java_home && Dir.exist?(java_home)
|
|
225
235
|
cmd_parts << "export JAVA_HOME=#{shell_escape(java_home)}"
|
|
226
|
-
cmd_parts <<
|
|
236
|
+
cmd_parts << '&&'
|
|
227
237
|
end
|
|
228
238
|
|
|
229
239
|
# Export ANDROID_HOME if we detected/fixed it
|
|
230
|
-
android_home = ENV
|
|
240
|
+
android_home = ENV.fetch('ANDROID_HOME', nil)
|
|
231
241
|
if android_home && Dir.exist?(android_home)
|
|
232
242
|
cmd_parts << "export ANDROID_HOME=#{shell_escape(android_home)}"
|
|
233
|
-
cmd_parts <<
|
|
243
|
+
cmd_parts << '&&'
|
|
234
244
|
cmd_parts << "export ANDROID_SDK_ROOT=#{shell_escape(android_home)}"
|
|
235
|
-
cmd_parts <<
|
|
245
|
+
cmd_parts << '&&'
|
|
236
246
|
end
|
|
237
|
-
|
|
247
|
+
|
|
248
|
+
# Phase 0: export signing env vars inline so they're only visible to
|
|
249
|
+
# the child process, not in argv. The Gradle init script below reads
|
|
250
|
+
# MYSIGNER_STORE_FILE / MYSIGNER_STORE_PASSWORD / MYSIGNER_KEY_ALIAS /
|
|
251
|
+
# MYSIGNER_KEY_PASSWORD and configures signingConfigs.release.
|
|
252
|
+
if @keystore_path && File.exist?(@keystore_path) && @signing_init_script_path
|
|
253
|
+
cmd_parts << "export MYSIGNER_STORE_FILE=#{shell_escape(File.absolute_path(@keystore_path))}"
|
|
254
|
+
cmd_parts << '&&'
|
|
255
|
+
cmd_parts << "export MYSIGNER_STORE_PASSWORD=#{shell_escape(@keystore_password)}" if @keystore_password
|
|
256
|
+
cmd_parts << '&&' if @keystore_password
|
|
257
|
+
cmd_parts << "export MYSIGNER_KEY_ALIAS=#{shell_escape(@key_alias)}" if @key_alias
|
|
258
|
+
cmd_parts << '&&' if @key_alias
|
|
259
|
+
cmd_parts << "export MYSIGNER_KEY_PASSWORD=#{shell_escape(@key_password)}" if @key_password
|
|
260
|
+
cmd_parts << '&&' if @key_password
|
|
261
|
+
end
|
|
262
|
+
|
|
238
263
|
# Change to android directory and run gradle
|
|
239
264
|
cmd_parts << "cd #{shell_escape(android_dir)}"
|
|
240
|
-
cmd_parts <<
|
|
265
|
+
cmd_parts << '&&'
|
|
241
266
|
cmd_parts << gradle_cmd
|
|
242
|
-
cmd_parts << task
|
|
243
267
|
|
|
244
|
-
#
|
|
245
|
-
if @
|
|
246
|
-
cmd_parts <<
|
|
247
|
-
cmd_parts <<
|
|
248
|
-
cmd_parts << "-Pandroid.injected.signing.key.alias=#{shell_escape(@key_alias)}" if @key_alias
|
|
249
|
-
cmd_parts << "-Pandroid.injected.signing.key.password=#{shell_escape(@key_password)}" if @key_password
|
|
268
|
+
# Reference the init script (contains the signing-config override)
|
|
269
|
+
if @signing_init_script_path
|
|
270
|
+
cmd_parts << '--init-script'
|
|
271
|
+
cmd_parts << shell_escape(@signing_init_script_path)
|
|
250
272
|
end
|
|
251
|
-
|
|
273
|
+
|
|
274
|
+
cmd_parts << task
|
|
275
|
+
|
|
252
276
|
# Add version code override if provided (no file modification needed)
|
|
253
|
-
if @version_code
|
|
254
|
-
cmd_parts << "-PversionCode=#{@version_code}"
|
|
255
|
-
end
|
|
277
|
+
cmd_parts << "-PversionCode=#{@version_code}" if @version_code
|
|
256
278
|
|
|
257
279
|
# Standard build options
|
|
258
|
-
cmd_parts <<
|
|
259
|
-
cmd_parts <<
|
|
280
|
+
cmd_parts << '--no-daemon' # Avoid daemon issues in CI
|
|
281
|
+
cmd_parts << '-q' # Quiet mode (less noise)
|
|
260
282
|
|
|
261
283
|
cmd_parts.join(' ')
|
|
262
284
|
end
|
|
@@ -266,29 +288,29 @@ module Mysigner
|
|
|
266
288
|
gradle_cmd = @parser.gradle_command
|
|
267
289
|
|
|
268
290
|
exports = []
|
|
269
|
-
java_home = ENV
|
|
291
|
+
java_home = ENV.fetch('JAVA_HOME', nil)
|
|
270
292
|
exports << "export JAVA_HOME=#{shell_escape(java_home)}" if java_home && Dir.exist?(java_home)
|
|
271
|
-
|
|
272
|
-
android_home = ENV
|
|
293
|
+
|
|
294
|
+
android_home = ENV.fetch('ANDROID_HOME', nil)
|
|
273
295
|
if android_home && Dir.exist?(android_home)
|
|
274
296
|
exports << "export ANDROID_HOME=#{shell_escape(android_home)}"
|
|
275
297
|
exports << "export ANDROID_SDK_ROOT=#{shell_escape(android_home)}"
|
|
276
298
|
end
|
|
277
|
-
|
|
278
|
-
export_str = exports.any? ? exports.join(' && ')
|
|
299
|
+
|
|
300
|
+
export_str = exports.any? ? "#{exports.join(' && ')} && " : ''
|
|
279
301
|
cmd = "#{export_str}cd #{shell_escape(android_dir)} && #{gradle_cmd} #{task} --no-daemon -q"
|
|
280
302
|
system(cmd)
|
|
281
303
|
end
|
|
282
304
|
|
|
283
305
|
def execute_with_output(cmd)
|
|
284
306
|
puts "🏗️ Running: gradle #{@variant}..."
|
|
285
|
-
puts
|
|
307
|
+
puts ''
|
|
286
308
|
|
|
287
309
|
# Run command and capture output in real-time
|
|
288
310
|
IO.popen("#{cmd} 2>&1", 'r') do |io|
|
|
289
311
|
io.each_line do |line|
|
|
290
312
|
next if line.strip.empty?
|
|
291
|
-
|
|
313
|
+
|
|
292
314
|
# Show errors and warnings
|
|
293
315
|
if line.include?('FAILURE') || line.include?('ERROR') || line.include?('error:')
|
|
294
316
|
puts line
|
|
@@ -304,14 +326,14 @@ module Mysigner
|
|
|
304
326
|
end
|
|
305
327
|
end
|
|
306
328
|
|
|
307
|
-
puts
|
|
329
|
+
puts '' # New line after dots
|
|
308
330
|
|
|
309
|
-
|
|
331
|
+
$CHILD_STATUS.success?
|
|
310
332
|
end
|
|
311
333
|
|
|
312
334
|
def find_aab_output(variant)
|
|
313
335
|
android_dir = @parser.android_directory
|
|
314
|
-
|
|
336
|
+
|
|
315
337
|
# Search patterns for AAB files
|
|
316
338
|
patterns = [
|
|
317
339
|
File.join(android_dir, "app/build/outputs/bundle/#{variant}/*.aab"),
|
|
@@ -332,7 +354,7 @@ module Mysigner
|
|
|
332
354
|
|
|
333
355
|
def find_apk_output(variant)
|
|
334
356
|
android_dir = @parser.android_directory
|
|
335
|
-
|
|
357
|
+
|
|
336
358
|
# Search patterns for APK files
|
|
337
359
|
patterns = [
|
|
338
360
|
File.join(android_dir, "app/build/outputs/apk/#{variant}/*.apk"),
|
|
@@ -353,14 +375,12 @@ module Mysigner
|
|
|
353
375
|
|
|
354
376
|
def shell_escape(str)
|
|
355
377
|
return "''" if str.nil? || str.empty?
|
|
356
|
-
|
|
378
|
+
|
|
357
379
|
# If string contains no special characters, return as-is
|
|
358
|
-
if str =~
|
|
359
|
-
|
|
360
|
-
end
|
|
361
|
-
|
|
380
|
+
return str if str =~ %r{\A[a-zA-Z0-9_.\-/]+\z}
|
|
381
|
+
|
|
362
382
|
# Otherwise, quote it
|
|
363
|
-
"'
|
|
383
|
+
"'#{str.gsub("'", "'\\''")}'"
|
|
364
384
|
end
|
|
365
385
|
end
|
|
366
386
|
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 =
|
|
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 = [
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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 =~
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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']
|
|
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
|
|
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
|
-
|
|
117
|
-
|
|
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
|
-
|