emerge 0.4.0 → 0.5.0
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/lib/commands/autofixes/exported_symbols.rb +62 -0
- data/lib/commands/autofixes/minify_strings.rb +99 -0
- data/lib/commands/autofixes/strip_binary_symbols.rb +116 -0
- data/lib/commands/build_distribution/download_and_install.rb +107 -0
- data/lib/commands/build_distribution/validate_app.rb +164 -0
- data/lib/commands/order_files/validate_xcode_project.rb +71 -0
- data/lib/commands/upload/build.rb +140 -0
- data/lib/emerge_cli.rb +20 -0
- data/lib/reaper/ast_parser.rb +17 -16
- data/lib/reaper/code_deleter.rb +3 -1
- data/lib/utils/git.rb +8 -0
- data/lib/utils/network.rb +2 -2
- data/lib/version.rb +1 -1
- data/parsers/libtree-sitter-objc-darwin-arm64.dylib +0 -0
- data/parsers/libtree-sitter-objc-linux-x86_64.so +0 -0
- metadata +28 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1ebd308782a251e11a4d515c0728b572456ed730ef2376f634939fec6ace820e
|
4
|
+
data.tar.gz: d476865caa4a62c32d9b258ec6fbfb474cb67c7bdf05c3ef1548ff860a1a9697
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 149980818e3fcb6741c029a601cb903dcd05051a4564889d41b8e49c4380a7f08662b26cbfe5eca0e3ced6b114a12075118fd749e88ccb27ec694a4352799f8d
|
7
|
+
data.tar.gz: d1ed903d5c8560a649fd4566a45067403fe506112d79ff6e686763bfee02d8c7c9a2823fc7a23fc5af4cf93f5c6a20fb82d59644fd3347b837256df43b09abae
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'dry/cli'
|
2
|
+
require 'xcodeproj'
|
3
|
+
|
4
|
+
module EmergeCLI
|
5
|
+
module Commands
|
6
|
+
module Autofixes
|
7
|
+
class ExportedSymbols < EmergeCLI::Commands::GlobalOptions
|
8
|
+
desc 'Remove exported symbols from built binaries'
|
9
|
+
|
10
|
+
option :path, type: :string, required: true, desc: 'Path to the xcarchive'
|
11
|
+
|
12
|
+
# Constants
|
13
|
+
DEFAULT_EXPORTED_SYMBOLS = %(_main
|
14
|
+
__mh_execute_header).freeze
|
15
|
+
EXPORTED_SYMBOLS_FILE = 'EXPORTED_SYMBOLS_FILE'.freeze
|
16
|
+
EXPORTED_SYMBOLS_PATH = '$(SRCROOT)/EmergeToolsHelperFiles/ExportedSymbols'.freeze
|
17
|
+
EXPORTED_SYMBOLS_FILE_NAME = 'ExportedSymbols'.freeze
|
18
|
+
EMERGE_TOOLS_GROUP = 'EmergeToolsHelperFiles'.freeze
|
19
|
+
|
20
|
+
def call(**options)
|
21
|
+
@options = options
|
22
|
+
before(options)
|
23
|
+
|
24
|
+
raise 'Path must be an xcodeproj' unless @options[:path].end_with?('.xcodeproj')
|
25
|
+
raise 'Path does not exist' unless File.exist?(@options[:path])
|
26
|
+
|
27
|
+
Sync do
|
28
|
+
project = Xcodeproj::Project.open(@options[:path])
|
29
|
+
|
30
|
+
# Add the exported symbols file to the project
|
31
|
+
group = project.main_group
|
32
|
+
emergetools_group = group.find_subpath(EMERGE_TOOLS_GROUP, true)
|
33
|
+
emergetools_group.set_path(EMERGE_TOOLS_GROUP)
|
34
|
+
|
35
|
+
unless emergetools_group.find_file_by_path(EXPORTED_SYMBOLS_FILE_NAME)
|
36
|
+
emergetools_group.new_file(EXPORTED_SYMBOLS_FILE_NAME)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Create Folder if it doesn't exist
|
40
|
+
|
41
|
+
FileUtils.mkdir_p(File.join(File.dirname(@options[:path]), EMERGE_TOOLS_GROUP))
|
42
|
+
|
43
|
+
# Create the exported symbols file
|
44
|
+
path = File.join(File.dirname(@options[:path]), EMERGE_TOOLS_GROUP, EXPORTED_SYMBOLS_FILE_NAME)
|
45
|
+
File.write(path, DEFAULT_EXPORTED_SYMBOLS)
|
46
|
+
|
47
|
+
project.targets.each do |target|
|
48
|
+
# Only do it for app targets
|
49
|
+
next unless target.product_type == 'com.apple.product-type.application'
|
50
|
+
|
51
|
+
target.build_configurations.each do |config|
|
52
|
+
config.build_settings[EXPORTED_SYMBOLS_FILE] = EXPORTED_SYMBOLS_PATH
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
project.save
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'dry/cli'
|
2
|
+
require 'xcodeproj'
|
3
|
+
|
4
|
+
module EmergeCLI
|
5
|
+
module Commands
|
6
|
+
module Autofixes
|
7
|
+
class MinifyStrings < EmergeCLI::Commands::GlobalOptions
|
8
|
+
desc 'Minify strings in the app'
|
9
|
+
|
10
|
+
option :path, type: :string, required: true, desc: 'Path to the xcarchive'
|
11
|
+
|
12
|
+
# Constants
|
13
|
+
SCRIPT_NAME = 'EmergeTools Minify Strings'.freeze
|
14
|
+
ENABLE_USER_SCRIPT_SANDBOXING = 'ENABLE_USER_SCRIPT_SANDBOXING'.freeze
|
15
|
+
STRINGS_FILE_OUTPUT_ENCODING = 'STRINGS_FILE_OUTPUT_ENCODING'.freeze
|
16
|
+
STRINGS_FILE_OUTPUT_ENCODING_VALUE = 'UTF-8'.freeze
|
17
|
+
SCRIPT_CONTENT = %{import os
|
18
|
+
import json
|
19
|
+
from multiprocessing.pool import ThreadPool
|
20
|
+
|
21
|
+
def minify(file_path):
|
22
|
+
os.system(f"plutil -convert json '{file_path}'")
|
23
|
+
new_content = ''
|
24
|
+
try:
|
25
|
+
with open(file_path, 'r') as input_file:
|
26
|
+
data = json.load(input_file)
|
27
|
+
|
28
|
+
for key, value in data.items():
|
29
|
+
fixed_key = json.dumps(key, ensure_ascii=False).encode('utf8').decode()
|
30
|
+
fixed_value = json.dumps(value, ensure_ascii=False).encode('utf8').decode()
|
31
|
+
new_line = f'{fixed_key} = {fixed_value};\\n'
|
32
|
+
new_content += new_line
|
33
|
+
|
34
|
+
with open(file_path, 'w') as output_file:
|
35
|
+
output_file.write(new_content)
|
36
|
+
except:
|
37
|
+
return
|
38
|
+
|
39
|
+
file_extension = '.strings'
|
40
|
+
stringFiles = []
|
41
|
+
|
42
|
+
for root, _, files in os.walk(os.environ['BUILT_PRODUCTS_DIR'], followlinks=True):
|
43
|
+
for filename in files:
|
44
|
+
if filename.endswith(file_extension):
|
45
|
+
input_path = os.path.join(root, filename)
|
46
|
+
stringFiles.append(input_path)
|
47
|
+
|
48
|
+
# create a thread pool
|
49
|
+
with ThreadPool() as pool:
|
50
|
+
pool.map(minify, stringFiles)
|
51
|
+
}.freeze
|
52
|
+
|
53
|
+
def call(**options)
|
54
|
+
@options = options
|
55
|
+
before(options)
|
56
|
+
|
57
|
+
raise 'Path must be an xcodeproj' unless @options[:path].end_with?('.xcodeproj')
|
58
|
+
raise 'Path does not exist' unless File.exist?(@options[:path])
|
59
|
+
|
60
|
+
Sync do
|
61
|
+
project = Xcodeproj::Project.open(@options[:path])
|
62
|
+
|
63
|
+
project.targets.each do |target|
|
64
|
+
target.build_configurations.each do |config|
|
65
|
+
enable_user_script_sandboxing(config)
|
66
|
+
set_output_encoding(config)
|
67
|
+
end
|
68
|
+
|
69
|
+
add_run_script(target)
|
70
|
+
end
|
71
|
+
|
72
|
+
project.save
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def enable_user_script_sandboxing(config)
|
79
|
+
Logger.info "Enabling user script sandboxing for #{config.name}"
|
80
|
+
config.build_settings[ENABLE_USER_SCRIPT_SANDBOXING] = 'NO'
|
81
|
+
end
|
82
|
+
|
83
|
+
def set_output_encoding(config)
|
84
|
+
Logger.info "Setting output encoding for #{config.name}"
|
85
|
+
config.build_settings[STRINGS_FILE_OUTPUT_ENCODING] = STRINGS_FILE_OUTPUT_ENCODING_VALUE
|
86
|
+
end
|
87
|
+
|
88
|
+
def add_run_script(target)
|
89
|
+
phase = target.shell_script_build_phases.find { |item| item.name == SCRIPT_NAME }
|
90
|
+
return unless phase.nil?
|
91
|
+
Logger.info "Creating script '#{SCRIPT_NAME}'"
|
92
|
+
phase = target.new_shell_script_build_phase(SCRIPT_NAME)
|
93
|
+
phase.shell_script = SCRIPT_CONTENT
|
94
|
+
phase.shell_path = `which python3`.strip
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'dry/cli'
|
2
|
+
require 'xcodeproj'
|
3
|
+
|
4
|
+
module EmergeCLI
|
5
|
+
module Commands
|
6
|
+
module Autofixes
|
7
|
+
class StripBinarySymbols < EmergeCLI::Commands::GlobalOptions
|
8
|
+
desc 'Strip binary symbols from the app'
|
9
|
+
|
10
|
+
option :path, type: :string, required: true, desc: 'Path to the xcarchive'
|
11
|
+
|
12
|
+
# Constants
|
13
|
+
SCRIPT_NAME = 'EmergeTools Strip Binary Symbols'.freeze
|
14
|
+
ENABLE_USER_SCRIPT_SANDBOXING = 'ENABLE_USER_SCRIPT_SANDBOXING'.freeze
|
15
|
+
INPUT_FILE = '${DWARF_DSYM_FOLDER_PATH}/${EXECUTABLE_NAME}.app.dSYM/' \
|
16
|
+
'Contents/Resources/DWARF/${EXECUTABLE_NAME}'.freeze
|
17
|
+
SCRIPT_CONTENT = %{#!/bin/bash
|
18
|
+
set -e
|
19
|
+
|
20
|
+
echo "Starting the symbol stripping process..."
|
21
|
+
|
22
|
+
if [ "Release" = "$\{CONFIGURATION\}" ]; then
|
23
|
+
echo "Configuration is Release."
|
24
|
+
|
25
|
+
# Path to the app directory
|
26
|
+
APP_DIR_PATH="$\{BUILT_PRODUCTS_DIR\}/$\{EXECUTABLE_FOLDER_PATH\}"
|
27
|
+
echo "App directory path: $\{APP_DIR_PATH\}"
|
28
|
+
|
29
|
+
# Strip main binary
|
30
|
+
echo "Stripping main binary: $\{APP_DIR_PATH\}/$\{EXECUTABLE_NAME\}"
|
31
|
+
strip -rSTx "$\{APP_DIR_PATH\}/$\{EXECUTABLE_NAME\}"
|
32
|
+
if [ $? -eq 0 ]; then
|
33
|
+
echo "Successfully stripped main binary."
|
34
|
+
else
|
35
|
+
echo "Failed to strip main binary." >&2
|
36
|
+
fi
|
37
|
+
|
38
|
+
# Path to the Frameworks directory
|
39
|
+
APP_FRAMEWORKS_DIR="$\{APP_DIR_PATH\}/Frameworks"
|
40
|
+
echo "Frameworks directory path: $\{APP_FRAMEWORKS_DIR\}"
|
41
|
+
|
42
|
+
# Strip symbols from frameworks, if Frameworks/ exists at all
|
43
|
+
# ... as long as the framework is NOT signed by Apple
|
44
|
+
if [ -d "$\{APP_FRAMEWORKS_DIR\}" ]; then
|
45
|
+
echo "Frameworks directory exists. Proceeding to strip symbols from frameworks."
|
46
|
+
find "$\{APP_FRAMEWORKS_DIR\}" -type f -perm +111 -maxdepth 2 -mindepth 2 -exec bash -c '
|
47
|
+
codesign -v -R="anchor apple" "\{\}" &> /dev/null ||
|
48
|
+
(
|
49
|
+
echo "Stripping \{\}" &&
|
50
|
+
if [ -w "\{\}" ]; then
|
51
|
+
strip -rSTx "\{\}"
|
52
|
+
if [ $? -eq 0 ]; then
|
53
|
+
echo "Successfully stripped \{\}"
|
54
|
+
else
|
55
|
+
echo "Failed to strip \{\}" >&2
|
56
|
+
fi
|
57
|
+
else
|
58
|
+
echo "Warning: No write permission for \{\}"
|
59
|
+
fi
|
60
|
+
)
|
61
|
+
' \\;
|
62
|
+
if [ $? -eq 0 ]; then
|
63
|
+
echo "Successfully stripped symbols from frameworks."
|
64
|
+
else
|
65
|
+
echo "Failed to strip symbols from some frameworks." >&2
|
66
|
+
fi
|
67
|
+
else
|
68
|
+
echo "Frameworks directory does not exist. Skipping framework stripping."
|
69
|
+
fi
|
70
|
+
else
|
71
|
+
echo "Configuration is not Release. Skipping symbol stripping."
|
72
|
+
fi
|
73
|
+
|
74
|
+
echo "Symbol stripping process completed."}.freeze
|
75
|
+
|
76
|
+
def call(**options)
|
77
|
+
@options = options
|
78
|
+
before(options)
|
79
|
+
|
80
|
+
raise 'Path must be an xcodeproj' unless @options[:path].end_with?('.xcodeproj')
|
81
|
+
raise 'Path does not exist' unless File.exist?(@options[:path])
|
82
|
+
|
83
|
+
Sync do
|
84
|
+
project = Xcodeproj::Project.open(@options[:path])
|
85
|
+
|
86
|
+
project.targets.each do |target|
|
87
|
+
target.build_configurations.each do |config|
|
88
|
+
enable_user_script_sandboxing(config)
|
89
|
+
end
|
90
|
+
|
91
|
+
add_run_script(target)
|
92
|
+
end
|
93
|
+
|
94
|
+
project.save
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def enable_user_script_sandboxing(config)
|
101
|
+
Logger.info "Enabling user script sandboxing for #{config.name}"
|
102
|
+
config.build_settings[ENABLE_USER_SCRIPT_SANDBOXING] = 'NO'
|
103
|
+
end
|
104
|
+
|
105
|
+
def add_run_script(target)
|
106
|
+
phase = target.shell_script_build_phases.find { |item| item.name == SCRIPT_NAME }
|
107
|
+
return unless phase.nil?
|
108
|
+
Logger.info "Creating script '#{SCRIPT_NAME}'"
|
109
|
+
phase = target.new_shell_script_build_phase(SCRIPT_NAME)
|
110
|
+
phase.shell_script = SCRIPT_CONTENT
|
111
|
+
phase.input_paths = [INPUT_FILE]
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require 'dry/cli'
|
2
|
+
require 'cfpropertylist'
|
3
|
+
require 'zip'
|
4
|
+
require 'rbconfig'
|
5
|
+
|
6
|
+
module EmergeCLI
|
7
|
+
module Commands
|
8
|
+
module BuildDistribution
|
9
|
+
class DownloadAndInstall < EmergeCLI::Commands::GlobalOptions
|
10
|
+
desc 'Download build from Build Distribution'
|
11
|
+
|
12
|
+
option :api_token, type: :string, required: false,
|
13
|
+
desc: 'API token for authentication, defaults to ENV[EMERGE_API_TOKEN]'
|
14
|
+
option :build_id, type: :string, required: true, desc: 'Build ID to download'
|
15
|
+
option :install, type: :boolean, default: true, required: false, desc: 'Install the build on the device'
|
16
|
+
option :device_id, type: :string, required: false, desc: 'Device id to install the build'
|
17
|
+
option :output, type: :string, required: false, desc: 'Output path for the downloaded build'
|
18
|
+
|
19
|
+
def initialize(network: nil)
|
20
|
+
@network = network
|
21
|
+
end
|
22
|
+
|
23
|
+
def call(**options)
|
24
|
+
@options = options
|
25
|
+
before(options)
|
26
|
+
|
27
|
+
Sync do
|
28
|
+
api_token = @options[:api_token] || ENV.fetch('EMERGE_API_TOKEN', nil)
|
29
|
+
raise 'API token is required' unless api_token
|
30
|
+
|
31
|
+
raise 'Build ID is required' unless @options[:build_id]
|
32
|
+
|
33
|
+
begin
|
34
|
+
@network ||= EmergeCLI::Network.new(api_token:)
|
35
|
+
|
36
|
+
Logger.info 'Getting build URL...'
|
37
|
+
request = get_build_url(@options[:build_id])
|
38
|
+
response = parse_response(request)
|
39
|
+
|
40
|
+
platform = response['platform']
|
41
|
+
download_url = response['downloadUrl']
|
42
|
+
|
43
|
+
extension = platform == 'ios' ? 'ipa' : 'apk'
|
44
|
+
Logger.info 'Downloading build...'
|
45
|
+
output_name = @options[:output] || "#{@options[:build_id]}.#{extension}"
|
46
|
+
`curl --progress-bar -L '#{download_url}' -o #{output_name} `
|
47
|
+
Logger.info "✅ Build downloaded to #{output_name}"
|
48
|
+
|
49
|
+
if @options[:install]
|
50
|
+
install_ios_build(output_name) if platform == 'ios'
|
51
|
+
install_android_build(output_name) if platform == 'android'
|
52
|
+
end
|
53
|
+
rescue StandardError => e
|
54
|
+
Logger.error "Failed to download build: #{e.message}"
|
55
|
+
Logger.error 'Check your parameters and try again'
|
56
|
+
raise e
|
57
|
+
ensure
|
58
|
+
@network&.close
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def get_build_url(build_id)
|
66
|
+
@network.get(
|
67
|
+
path: '/distribution/downloadUrl',
|
68
|
+
max_retries: 3,
|
69
|
+
query: {
|
70
|
+
buildId: build_id
|
71
|
+
}
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
def parse_response(response)
|
76
|
+
case response.status
|
77
|
+
when 200
|
78
|
+
JSON.parse(response.read)
|
79
|
+
when 400
|
80
|
+
error_message = JSON.parse(response.read)['errorMessage']
|
81
|
+
raise "Invalid parameters: #{error_message}"
|
82
|
+
when 401, 403
|
83
|
+
raise 'Invalid API token'
|
84
|
+
else
|
85
|
+
raise "Getting build failed with status #{response.status}"
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def install_ios_build(build_path)
|
90
|
+
command = "xcrun devicectl device install app -d #{@options[:device_id]} #{build_path}"
|
91
|
+
Logger.debug "Running command: #{command}"
|
92
|
+
`#{command}`
|
93
|
+
|
94
|
+
Logger.info '✅ Build installed'
|
95
|
+
end
|
96
|
+
|
97
|
+
def install_android_build(build_path)
|
98
|
+
command = "adb -s #{@options[:device_id]} install #{build_path}"
|
99
|
+
Logger.debug "Running command: #{command}"
|
100
|
+
`#{command}`
|
101
|
+
|
102
|
+
Logger.info '✅ Build installed'
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
require 'dry/cli'
|
2
|
+
require 'cfpropertylist'
|
3
|
+
require 'zip'
|
4
|
+
require 'rbconfig'
|
5
|
+
|
6
|
+
module EmergeCLI
|
7
|
+
module Commands
|
8
|
+
module BuildDistribution
|
9
|
+
class ValidateApp < EmergeCLI::Commands::GlobalOptions
|
10
|
+
desc 'Validate app for build distribution'
|
11
|
+
|
12
|
+
option :path, type: :string, required: true, desc: 'Path to the xcarchive, IPA or APK to validate'
|
13
|
+
|
14
|
+
# Constants
|
15
|
+
PLIST_START = '<plist'.freeze
|
16
|
+
PLIST_STOP = '</plist>'.freeze
|
17
|
+
|
18
|
+
UTF8_ENCODING = 'UTF-8'.freeze
|
19
|
+
STRING_FORMAT = 'binary'.freeze
|
20
|
+
EMPTY_STRING = ''.freeze
|
21
|
+
|
22
|
+
EXPECTED_ABI = 'arm64-v8a'.freeze
|
23
|
+
|
24
|
+
def call(**options)
|
25
|
+
@options = options
|
26
|
+
before(options)
|
27
|
+
|
28
|
+
Sync do
|
29
|
+
file_extension = File.extname(@options[:path])
|
30
|
+
case file_extension
|
31
|
+
when '.xcarchive'
|
32
|
+
handle_xcarchive
|
33
|
+
when '.ipa'
|
34
|
+
handle_ipa
|
35
|
+
when '.app'
|
36
|
+
handle_app
|
37
|
+
when '.apk'
|
38
|
+
handle_apk
|
39
|
+
else
|
40
|
+
raise "Unknown file extension: #{file_extension}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def handle_xcarchive
|
48
|
+
raise 'Path must be an xcarchive' unless @options[:path].end_with?('.xcarchive')
|
49
|
+
|
50
|
+
app_path = Dir.glob("#{@options[:path]}/Products/Applications/*.app").first
|
51
|
+
run_codesign_check(app_path)
|
52
|
+
read_provisioning_profile(app_path)
|
53
|
+
end
|
54
|
+
|
55
|
+
def handle_ipa
|
56
|
+
raise 'Path must be an IPA' unless @options[:path].end_with?('.ipa')
|
57
|
+
|
58
|
+
Dir.mktmpdir do |tmp_dir|
|
59
|
+
Zip::File.open(@options[:path]) do |zip_file|
|
60
|
+
zip_file.each do |entry|
|
61
|
+
entry.extract(File.join(tmp_dir, entry.name))
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
app_path = File.join(tmp_dir, 'Payload/*.app')
|
66
|
+
app_path = Dir.glob(app_path).first
|
67
|
+
run_codesign_check(app_path)
|
68
|
+
read_provisioning_profile(app_path)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def handle_app
|
73
|
+
raise 'Path must be an app' unless @options[:path].end_with?('.app')
|
74
|
+
|
75
|
+
app_path = @options[:path]
|
76
|
+
run_codesign_check(app_path)
|
77
|
+
read_provisioning_profile(app_path)
|
78
|
+
end
|
79
|
+
|
80
|
+
def handle_apk
|
81
|
+
raise 'Path must be an APK' unless @options[:path].end_with?('.apk')
|
82
|
+
|
83
|
+
apk_path = @options[:path]
|
84
|
+
check_supported_abis(apk_path)
|
85
|
+
end
|
86
|
+
|
87
|
+
def run_codesign_check(app_path)
|
88
|
+
unless RbConfig::CONFIG['host_os'] =~ /darwin/i
|
89
|
+
Logger.info 'Skipping codesign check on non-macOS platform'
|
90
|
+
return
|
91
|
+
end
|
92
|
+
|
93
|
+
command = "codesign -dvvv '#{app_path}'"
|
94
|
+
Logger.debug command
|
95
|
+
stdout, _, status = Open3.capture3(command)
|
96
|
+
Logger.debug stdout
|
97
|
+
raise '❌ Codesign check failed' unless status.success?
|
98
|
+
|
99
|
+
Logger.info '✅ Codesign check passed'
|
100
|
+
end
|
101
|
+
|
102
|
+
def read_provisioning_profile(app_path)
|
103
|
+
entitlements_path = File.join(app_path, 'embedded.mobileprovision')
|
104
|
+
raise '❌ Entitlements file not found' unless File.exist?(entitlements_path)
|
105
|
+
|
106
|
+
content = File.read(entitlements_path)
|
107
|
+
lines = content.lines
|
108
|
+
|
109
|
+
buffer = ''
|
110
|
+
inside_plist = false
|
111
|
+
lines.each do |line|
|
112
|
+
inside_plist = true if line.include? PLIST_START
|
113
|
+
if inside_plist
|
114
|
+
buffer << line
|
115
|
+
break if line.include? PLIST_STOP
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
encoded_plist = buffer.encode(UTF8_ENCODING, STRING_FORMAT, invalid: :replace, undef: :replace,
|
120
|
+
replace: EMPTY_STRING)
|
121
|
+
encoded_plist = encoded_plist.sub(/#{PLIST_STOP}.+/, PLIST_STOP)
|
122
|
+
|
123
|
+
plist = CFPropertyList::List.new(data: encoded_plist)
|
124
|
+
parsed_data = CFPropertyList.native_types(plist.value)
|
125
|
+
|
126
|
+
expiration_date = parsed_data['ExpirationDate']
|
127
|
+
if expiration_date > Time.now
|
128
|
+
Logger.info '✅ Provisioning profile hasn\'t expired'
|
129
|
+
else
|
130
|
+
Logger.info "❌ Provisioning profile is expired. Expiration date: #{expiration_date}"
|
131
|
+
end
|
132
|
+
|
133
|
+
provisions_all_devices = parsed_data['ProvisionsAllDevices']
|
134
|
+
if provisions_all_devices
|
135
|
+
Logger.info 'Provisioning profile supports all devices (likely an enterprise profile)'
|
136
|
+
else
|
137
|
+
devices = parsed_data['ProvisionedDevices']
|
138
|
+
Logger.info 'Provisioning profile does not support all devices (likely a development profile).'
|
139
|
+
Logger.info "Devices: #{devices.inspect}"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def check_supported_abis(apk_path)
|
144
|
+
abis = []
|
145
|
+
|
146
|
+
Zip::File.open(apk_path) do |zip_file|
|
147
|
+
zip_file.each do |entry|
|
148
|
+
if entry.name.start_with?('lib/') && entry.name.count('/') == 2
|
149
|
+
abi = entry.name.split('/')[1]
|
150
|
+
abis << abi unless abis.include?(abi)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
unless abis.include?(EXPECTED_ABI)
|
156
|
+
raise "APK does not support #{EXPECTED_ABI} architecture, found: #{abis.join(', ')}"
|
157
|
+
end
|
158
|
+
|
159
|
+
Logger.info "✅ APK supports #{EXPECTED_ABI} architecture"
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'dry/cli'
|
2
|
+
require 'xcodeproj'
|
3
|
+
|
4
|
+
module EmergeCLI
|
5
|
+
module Commands
|
6
|
+
class ValidateXcodeProject < EmergeCLI::Commands::GlobalOptions
|
7
|
+
desc 'Validate xcodeproject for order files'
|
8
|
+
|
9
|
+
option :path, type: :string, required: true, desc: 'Path to the xcodeproject to validate'
|
10
|
+
option :target, type: :string, required: false, desc: 'Target to validate'
|
11
|
+
option :build_configuration, type: :string, required: false,
|
12
|
+
desc: 'Build configuration to validate (Release by default)'
|
13
|
+
|
14
|
+
# Constants
|
15
|
+
LINK_MAPS_CONFIG = 'LD_GENERATE_MAP_FILE'.freeze
|
16
|
+
LINK_MAPS_PATH = 'LD_MAP_FILE_PATH'.freeze
|
17
|
+
PATH_TO_LINKMAP = '$(TARGET_TEMP_DIR)/$(PRODUCT_NAME)-LinkMap-$(CURRENT_VARIANT)-$(CURRENT_ARCH).txt'.freeze
|
18
|
+
|
19
|
+
def call(**options)
|
20
|
+
@options = options
|
21
|
+
before(options)
|
22
|
+
|
23
|
+
raise 'Path must be an xcodeproject' unless @options[:path].end_with?('.xcodeproj')
|
24
|
+
raise 'Path does not exist' unless File.exist?(@options[:path])
|
25
|
+
|
26
|
+
@options[:build_configuration] ||= 'Release'
|
27
|
+
|
28
|
+
Sync do
|
29
|
+
project = Xcodeproj::Project.open(@options[:path])
|
30
|
+
|
31
|
+
validate_xcproj(project)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def validate_xcproj(project)
|
38
|
+
project.targets.each do |target|
|
39
|
+
next if @options[:target] && target.name != @options[:target]
|
40
|
+
next unless target.product_type == 'com.apple.product-type.application'
|
41
|
+
|
42
|
+
target.build_configurations.each do |config|
|
43
|
+
next if config.name != @options[:build_configuration]
|
44
|
+
validate_target_config(target, config)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def validate_target_config(target, config)
|
50
|
+
has_error = false
|
51
|
+
if config.build_settings[LINK_MAPS_CONFIG] != 'YES'
|
52
|
+
has_error = true
|
53
|
+
Logger.error "❌ Write Link Map File (#{LINK_MAPS_CONFIG}) is not set to YES"
|
54
|
+
end
|
55
|
+
if config.build_settings[LINK_MAPS_PATH] != ''
|
56
|
+
has_error = true
|
57
|
+
Logger.error "❌ Path to Link Map File (#{LINK_MAPS_PATH}) is not set, we recommend \
|
58
|
+
setting it to '#{PATH_TO_LINKMAP}'"
|
59
|
+
end
|
60
|
+
|
61
|
+
if has_error
|
62
|
+
Logger.error "❌ Target '#{target.name}' has errors, this means \
|
63
|
+
that the linkmaps will not be generated as expected"
|
64
|
+
Logger.error "Use `emerge configure order-files-ios --project-path '#{@options[:path]}'` to fix this"
|
65
|
+
else
|
66
|
+
Logger.info "✅ Target '#{target.name}' is valid"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
require 'dry/cli'
|
2
|
+
require 'json'
|
3
|
+
require 'uri'
|
4
|
+
require 'async'
|
5
|
+
require 'async/barrier'
|
6
|
+
require 'async/semaphore'
|
7
|
+
require 'async/http/internet/instance'
|
8
|
+
|
9
|
+
module EmergeCLI
|
10
|
+
module Commands
|
11
|
+
module Upload
|
12
|
+
class Build < EmergeCLI::Commands::GlobalOptions
|
13
|
+
desc 'Upload a build to Emerge'
|
14
|
+
|
15
|
+
option :path, type: :string, required: true, desc: 'Path to the build artifact'
|
16
|
+
|
17
|
+
# Optional options
|
18
|
+
option :api_token, type: :string, required: false,
|
19
|
+
desc: 'API token for authentication, defaults to ENV[EMERGE_API_TOKEN]'
|
20
|
+
option :sha, type: :string, required: false, desc: 'SHA of the commit'
|
21
|
+
option :branch, type: :string, required: false, desc: 'Branch name'
|
22
|
+
option :repo_name, type: :string, required: false, desc: 'Repository name'
|
23
|
+
option :base_sha, type: :string, required: false, desc: 'Base SHA'
|
24
|
+
option :previous_sha, type: :string, required: false, desc: 'Previous SHA'
|
25
|
+
option :pr_number, type: :string, required: false, desc: 'PR number'
|
26
|
+
|
27
|
+
def initialize(network: nil, git_info_provider: nil)
|
28
|
+
@network = network
|
29
|
+
@git_info_provider = git_info_provider
|
30
|
+
end
|
31
|
+
|
32
|
+
def call(**options)
|
33
|
+
@options = options
|
34
|
+
@profiler = EmergeCLI::Profiler.new(enabled: options[:profile])
|
35
|
+
before(options)
|
36
|
+
|
37
|
+
start_time = Time.now
|
38
|
+
|
39
|
+
file_path = options[:path]
|
40
|
+
file_exists = File.exist?(file_path)
|
41
|
+
raise "File not found at path: #{file_path}" unless file_exists
|
42
|
+
|
43
|
+
file_extension = File.extname(file_path)
|
44
|
+
raise "Unsupported file type: #{file_extension}" unless ['.ipa', '.apk', '.aab',
|
45
|
+
'.zip'].include?(file_extension)
|
46
|
+
|
47
|
+
api_token = @options[:api_token] || ENV.fetch('EMERGE_API_TOKEN', nil)
|
48
|
+
raise 'API token is required and cannot be blank' if api_token.nil? || api_token.strip.empty?
|
49
|
+
|
50
|
+
@network ||= EmergeCLI::Network.new(api_token:)
|
51
|
+
@git_info_provider ||= GitInfoProvider.new
|
52
|
+
|
53
|
+
Sync do
|
54
|
+
upload_url, upload_id = fetch_upload_url
|
55
|
+
|
56
|
+
file_size = File.size(file_path)
|
57
|
+
Logger.info("Uploading file... (#{file_size} bytes)")
|
58
|
+
|
59
|
+
File.open(file_path, 'rb') do |file|
|
60
|
+
headers = {
|
61
|
+
'Content-Type' => 'application/zip',
|
62
|
+
'Content-Length' => file_size.to_s
|
63
|
+
}
|
64
|
+
|
65
|
+
response = @network.put(
|
66
|
+
path: upload_url,
|
67
|
+
body: file.read,
|
68
|
+
headers: headers
|
69
|
+
)
|
70
|
+
|
71
|
+
unless response.status == 200
|
72
|
+
Logger.error("Upload failed with status #{response.status}")
|
73
|
+
Logger.error("Response body: #{response.body}")
|
74
|
+
raise "Uploading file failed with status #{response.status}"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
Logger.info('Upload complete successfully!')
|
79
|
+
Logger.info "Time taken: #{(Time.now - start_time).round(2)} seconds"
|
80
|
+
Logger.info("✅ You can view the build analysis at https://emergetools.com/build/#{upload_id}")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def fetch_upload_url
|
87
|
+
git_result = @git_info_provider.fetch_git_info
|
88
|
+
sha = @options[:sha] || git_result.sha
|
89
|
+
branch = @options[:branch] || git_result.branch
|
90
|
+
base_sha = @options[:base_sha] || git_result.base_sha
|
91
|
+
previous_sha = @options[:previous_sha] || git_result.previous_sha
|
92
|
+
pr_number = @options[:pr_number] || git_result.pr_number
|
93
|
+
|
94
|
+
# TODO: Make optional
|
95
|
+
raise 'SHA is required' unless sha
|
96
|
+
raise 'Branch is required' unless branch
|
97
|
+
|
98
|
+
payload = {
|
99
|
+
sha:,
|
100
|
+
branch:,
|
101
|
+
repo_name: @options[:repo_name],
|
102
|
+
# Optional
|
103
|
+
base_sha:,
|
104
|
+
previous_sha:,
|
105
|
+
pr_number: pr_number&.to_s
|
106
|
+
}.compact
|
107
|
+
|
108
|
+
upload_response = @network.post(
|
109
|
+
path: '/upload',
|
110
|
+
body: payload,
|
111
|
+
headers: { 'Content-Type' => 'application/json' }
|
112
|
+
)
|
113
|
+
upload_json = parse_response(upload_response)
|
114
|
+
upload_id = upload_json.fetch('upload_id')
|
115
|
+
upload_url = upload_json.fetch('uploadURL')
|
116
|
+
Logger.debug("Got upload ID: #{upload_id}")
|
117
|
+
|
118
|
+
warning = upload_json['warning']
|
119
|
+
Logger.warn(warning) if warning
|
120
|
+
|
121
|
+
[upload_url, upload_id]
|
122
|
+
end
|
123
|
+
|
124
|
+
def parse_response(response)
|
125
|
+
case response.status
|
126
|
+
when 200
|
127
|
+
JSON.parse(response.read)
|
128
|
+
when 400
|
129
|
+
error_message = JSON.parse(response.read)['errorMessage']
|
130
|
+
raise "Invalid parameters: #{error_message}"
|
131
|
+
when 401, 403
|
132
|
+
raise 'Invalid API token'
|
133
|
+
else
|
134
|
+
raise "Creating upload failed with status #{response.status}"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
data/lib/emerge_cli.rb
CHANGED
@@ -13,6 +13,13 @@ require_relative 'commands/reaper/reaper'
|
|
13
13
|
require_relative 'commands/snapshots/validate_app'
|
14
14
|
require_relative 'commands/order_files/download_order_files'
|
15
15
|
require_relative 'commands/order_files/validate_linkmaps'
|
16
|
+
require_relative 'commands/order_files/validate_xcode_project'
|
17
|
+
require_relative 'commands/upload/build'
|
18
|
+
require_relative 'commands/build_distribution/validate_app'
|
19
|
+
require_relative 'commands/build_distribution/download_and_install'
|
20
|
+
require_relative 'commands/autofixes/minify_strings'
|
21
|
+
require_relative 'commands/autofixes/strip_binary_symbols'
|
22
|
+
require_relative 'commands/autofixes/exported_symbols'
|
16
23
|
|
17
24
|
require_relative 'reaper/ast_parser'
|
18
25
|
require_relative 'reaper/code_deleter'
|
@@ -34,6 +41,7 @@ module EmergeCLI
|
|
34
41
|
extend Dry::CLI::Registry
|
35
42
|
|
36
43
|
register 'upload', aliases: ['u'] do |prefix|
|
44
|
+
prefix.register 'build', Commands::Upload::Build
|
37
45
|
prefix.register 'snapshots', Commands::Upload::Snapshots
|
38
46
|
end
|
39
47
|
|
@@ -55,6 +63,18 @@ module EmergeCLI
|
|
55
63
|
register 'order-files' do |prefix|
|
56
64
|
prefix.register 'download', Commands::DownloadOrderFiles
|
57
65
|
prefix.register 'validate-linkmaps', Commands::ValidateLinkmaps
|
66
|
+
prefix.register 'validate-xcode-project', Commands::ValidateXcodeProject
|
67
|
+
end
|
68
|
+
|
69
|
+
register 'build-distribution' do |prefix|
|
70
|
+
prefix.register 'validate-app', Commands::BuildDistribution::ValidateApp
|
71
|
+
prefix.register 'install', Commands::BuildDistribution::DownloadAndInstall
|
72
|
+
end
|
73
|
+
|
74
|
+
register 'autofix' do |prefix|
|
75
|
+
prefix.register 'minify-strings', Commands::Autofixes::MinifyStrings
|
76
|
+
prefix.register 'strip-binary-symbols', Commands::Autofixes::StripBinarySymbols
|
77
|
+
prefix.register 'exported-symbols', Commands::Autofixes::ExportedSymbols
|
58
78
|
end
|
59
79
|
end
|
60
80
|
|
data/lib/reaper/ast_parser.rb
CHANGED
@@ -9,19 +9,22 @@ module EmergeCLI
|
|
9
9
|
DECLARATION_NODE_TYPES = {
|
10
10
|
'swift' => %i[class_declaration protocol_declaration],
|
11
11
|
'kotlin' => %i[class_declaration protocol_declaration interface_declaration object_declaration],
|
12
|
-
'java' => %i[class_declaration protocol_declaration interface_declaration]
|
12
|
+
'java' => %i[class_declaration protocol_declaration interface_declaration],
|
13
|
+
'objc' => %i[class_declaration protocol_declaration class_implementation class_interface]
|
13
14
|
}.freeze
|
14
15
|
|
15
16
|
IDENTIFIER_NODE_TYPES = {
|
16
17
|
'swift' => %i[simple_identifier qualified_name identifier type_identifier],
|
17
18
|
'kotlin' => %i[simple_identifier qualified_name identifier type_identifier],
|
18
|
-
'java' => %i[simple_identifier qualified_name identifier type_identifier]
|
19
|
+
'java' => %i[simple_identifier qualified_name identifier type_identifier],
|
20
|
+
'objc' => %i[simple_identifier qualified_name identifier type_identifier]
|
19
21
|
}.freeze
|
20
22
|
|
21
23
|
COMMENT_AND_IMPORT_NODE_TYPES = {
|
22
24
|
'swift' => %i[comment import_declaration],
|
23
25
|
'kotlin' => %i[comment import_header],
|
24
|
-
'java' => %i[comment import_declaration]
|
26
|
+
'java' => %i[comment import_declaration],
|
27
|
+
'objc' => %i[comment import_declaration preproc_include]
|
25
28
|
}.freeze
|
26
29
|
|
27
30
|
attr_reader :parser, :language
|
@@ -52,17 +55,9 @@ module EmergeCLI
|
|
52
55
|
extension = platform == 'darwin' ? 'dylib' : 'so'
|
53
56
|
parser_file = "libtree-sitter-#{language}-#{platform}-#{arch}.#{extension}"
|
54
57
|
parser_path = File.join('parsers', parser_file)
|
58
|
+
raise "No language grammar found for #{language}" unless File.exist?(parser_path)
|
55
59
|
|
56
|
-
|
57
|
-
when 'swift'
|
58
|
-
@parser.language = TreeSitter::Language.load('swift', parser_path)
|
59
|
-
when 'kotlin'
|
60
|
-
@parser.language = TreeSitter::Language.load('kotlin', parser_path)
|
61
|
-
when 'java'
|
62
|
-
@parser.language = TreeSitter::Language.load('java', parser_path)
|
63
|
-
else
|
64
|
-
raise "Unsupported language: #{language}"
|
65
|
-
end
|
60
|
+
@parser.language = TreeSitter::Language.load(language, parser_path)
|
66
61
|
end
|
67
62
|
|
68
63
|
# Deletes a type from the given file contents.
|
@@ -127,6 +122,7 @@ module EmergeCLI
|
|
127
122
|
|
128
123
|
while (node = nodes_to_process.shift)
|
129
124
|
identifier_type = identifier_node_types.include?(node.type)
|
125
|
+
Logger.debug "Processing node: #{node.type} #{node_text(node)}"
|
130
126
|
declaration_type = if node == tree.root_node
|
131
127
|
false
|
132
128
|
else
|
@@ -136,6 +132,11 @@ module EmergeCLI
|
|
136
132
|
usages << { line: node.start_point.row, usage_type: 'declaration' }
|
137
133
|
elsif identifier_type && node_text(node) == type_name
|
138
134
|
usages << { line: node.start_point.row, usage_type: 'identifier' }
|
135
|
+
elsif node.type == :@implementation
|
136
|
+
next_sibling = node.next_named_sibling
|
137
|
+
if next_sibling.type == :identifier && node_text(next_sibling) == type_name
|
138
|
+
usages << { line: next_sibling.start_point.row, usage_type: 'declaration' }
|
139
|
+
end
|
139
140
|
end
|
140
141
|
|
141
142
|
node.each { |child| nodes_to_process.push(child) }
|
@@ -172,14 +173,14 @@ module EmergeCLI
|
|
172
173
|
|
173
174
|
return file_contents if nodes_to_remove.empty?
|
174
175
|
|
175
|
-
Logger.debug "Found #{nodes_to_remove.length} nodes to remove"
|
176
|
+
Logger.debug "✅ Found #{nodes_to_remove.length} nodes to remove"
|
176
177
|
remove_nodes_from_content(file_contents, nodes_to_remove)
|
177
178
|
end
|
178
179
|
|
179
180
|
private
|
180
181
|
|
181
182
|
def remove_node(node, lines_to_remove)
|
182
|
-
Logger.debug "Removing node: #{node.type}"
|
183
|
+
Logger.debug "✅ Removing node: #{node.type}"
|
183
184
|
start_position = node.start_point.row
|
184
185
|
end_position = node.end_point.row
|
185
186
|
lines_to_remove << { start: start_position, end: end_position }
|
@@ -287,7 +288,7 @@ module EmergeCLI
|
|
287
288
|
when :navigation_expression # NetworkDebugger.printStats
|
288
289
|
result = handle_navigation_expression(current)
|
289
290
|
return result if result
|
290
|
-
when :class_declaration, :function_declaration, :method_declaration
|
291
|
+
when :class_declaration, :function_declaration, :method_declaration, :@implementation
|
291
292
|
Logger.debug "Reached structural element, stopping at: #{current.type}"
|
292
293
|
break
|
293
294
|
end
|
data/lib/reaper/code_deleter.rb
CHANGED
@@ -163,7 +163,8 @@ module EmergeCLI
|
|
163
163
|
found_usages = []
|
164
164
|
source_patterns = case @platform&.downcase
|
165
165
|
when 'ios'
|
166
|
-
{ 'swift' => '**/*.swift'
|
166
|
+
{ 'swift' => '**/*.swift',
|
167
|
+
'objc' => '**/*.{m,h}' }
|
167
168
|
when 'android'
|
168
169
|
{
|
169
170
|
'kotlin' => '**/*.kt',
|
@@ -253,6 +254,7 @@ module EmergeCLI
|
|
253
254
|
when '.swift' then 'swift'
|
254
255
|
when '.kt' then 'kotlin'
|
255
256
|
when '.java' then 'java'
|
257
|
+
when '.m', '.h' then 'objc'
|
256
258
|
else
|
257
259
|
raise "Unsupported file type for #{file_path}"
|
258
260
|
end
|
data/lib/utils/git.rb
CHANGED
@@ -3,6 +3,7 @@ require 'open3'
|
|
3
3
|
module EmergeCLI
|
4
4
|
module Git
|
5
5
|
def self.branch
|
6
|
+
Logger.debug 'Getting current branch name'
|
6
7
|
command = 'git rev-parse --abbrev-ref HEAD'
|
7
8
|
Logger.debug command
|
8
9
|
stdout, _, status = Open3.capture3(command)
|
@@ -39,6 +40,7 @@ module EmergeCLI
|
|
39
40
|
end
|
40
41
|
|
41
42
|
def self.sha
|
43
|
+
Logger.debug 'Getting current SHA'
|
42
44
|
command = 'git rev-parse HEAD'
|
43
45
|
Logger.debug command
|
44
46
|
stdout, _, status = Open3.capture3(command)
|
@@ -46,6 +48,7 @@ module EmergeCLI
|
|
46
48
|
end
|
47
49
|
|
48
50
|
def self.base_sha
|
51
|
+
Logger.debug 'Getting base SHA'
|
49
52
|
current_branch = branch
|
50
53
|
remote_head = remote_head_branch
|
51
54
|
return nil if current_branch.nil? || remote_head.nil?
|
@@ -59,6 +62,7 @@ module EmergeCLI
|
|
59
62
|
end
|
60
63
|
|
61
64
|
def self.previous_sha
|
65
|
+
Logger.debug 'Getting previous SHA'
|
62
66
|
command = 'git rev-list --count HEAD'
|
63
67
|
Logger.debug command
|
64
68
|
count_stdout, _, count_status = Open3.capture3(command)
|
@@ -78,12 +82,14 @@ module EmergeCLI
|
|
78
82
|
end
|
79
83
|
|
80
84
|
def self.primary_remote
|
85
|
+
Logger.debug 'Getting primary remote'
|
81
86
|
remote = remote()
|
82
87
|
return nil if remote.nil?
|
83
88
|
remote.include?('origin') ? 'origin' : remote.first
|
84
89
|
end
|
85
90
|
|
86
91
|
def self.remote_head_branch(remote = primary_remote)
|
92
|
+
Logger.debug 'Getting remote head branch'
|
87
93
|
return nil if remote.nil?
|
88
94
|
command = "git remote show #{remote}"
|
89
95
|
Logger.debug command
|
@@ -98,6 +104,7 @@ module EmergeCLI
|
|
98
104
|
end
|
99
105
|
|
100
106
|
def self.remote_url(remote = primary_remote)
|
107
|
+
Logger.debug 'Getting remote URL'
|
101
108
|
return nil if remote.nil?
|
102
109
|
command = "git config --get remote.#{remote}.url"
|
103
110
|
Logger.debug command
|
@@ -106,6 +113,7 @@ module EmergeCLI
|
|
106
113
|
end
|
107
114
|
|
108
115
|
def self.remote
|
116
|
+
Logger.debug 'Getting remote'
|
109
117
|
command = 'git remote'
|
110
118
|
Logger.debug command
|
111
119
|
stdout, _, status = Open3.capture3(command)
|
data/lib/utils/network.rb
CHANGED
@@ -17,8 +17,8 @@ module EmergeCLI
|
|
17
17
|
@internet = Async::HTTP::Internet.new
|
18
18
|
end
|
19
19
|
|
20
|
-
def get(path:, headers: {}, max_retries: MAX_RETRIES)
|
21
|
-
request(:get, path, nil, headers,
|
20
|
+
def get(path:, headers: {}, query: nil, max_retries: MAX_RETRIES)
|
21
|
+
request(:get, path, nil, headers, query, max_retries)
|
22
22
|
end
|
23
23
|
|
24
24
|
def post(path:, body:, headers: {}, query: nil, max_retries: MAX_RETRIES)
|
data/lib/version.rb
CHANGED
Binary file
|
Binary file
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: emerge
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Emerge Tools
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2025-01-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: async-http
|
@@ -114,6 +114,20 @@ dependencies:
|
|
114
114
|
- - "~>"
|
115
115
|
- !ruby/object:Gem::Version
|
116
116
|
version: '1.9'
|
117
|
+
- !ruby/object:Gem::Dependency
|
118
|
+
name: rubyzip
|
119
|
+
requirement: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - "~>"
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: 2.3.0
|
124
|
+
type: :runtime
|
125
|
+
prerelease: false
|
126
|
+
version_requirements: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - "~>"
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: 2.3.0
|
117
131
|
- !ruby/object:Gem::Dependency
|
118
132
|
name: tty-prompt
|
119
133
|
requirement: !ruby/object:Gem::Requirement
|
@@ -167,14 +181,21 @@ files:
|
|
167
181
|
- CHANGELOG.md
|
168
182
|
- README.md
|
169
183
|
- exe/emerge
|
184
|
+
- lib/commands/autofixes/exported_symbols.rb
|
185
|
+
- lib/commands/autofixes/minify_strings.rb
|
186
|
+
- lib/commands/autofixes/strip_binary_symbols.rb
|
187
|
+
- lib/commands/build_distribution/download_and_install.rb
|
188
|
+
- lib/commands/build_distribution/validate_app.rb
|
170
189
|
- lib/commands/config/orderfiles/orderfiles_ios.rb
|
171
190
|
- lib/commands/config/snapshots/snapshots_ios.rb
|
172
191
|
- lib/commands/global_options.rb
|
173
192
|
- lib/commands/integrate/fastlane.rb
|
174
193
|
- lib/commands/order_files/download_order_files.rb
|
175
194
|
- lib/commands/order_files/validate_linkmaps.rb
|
195
|
+
- lib/commands/order_files/validate_xcode_project.rb
|
176
196
|
- lib/commands/reaper/reaper.rb
|
177
197
|
- lib/commands/snapshots/validate_app.rb
|
198
|
+
- lib/commands/upload/build.rb
|
178
199
|
- lib/commands/upload/snapshots/client_libraries/default.rb
|
179
200
|
- lib/commands/upload/snapshots/client_libraries/paparazzi.rb
|
180
201
|
- lib/commands/upload/snapshots/client_libraries/roborazzi.rb
|
@@ -198,6 +219,8 @@ files:
|
|
198
219
|
- parsers/libtree-sitter-java-linux-x86_64.so
|
199
220
|
- parsers/libtree-sitter-kotlin-darwin-arm64.dylib
|
200
221
|
- parsers/libtree-sitter-kotlin-linux-x86_64.so
|
222
|
+
- parsers/libtree-sitter-objc-darwin-arm64.dylib
|
223
|
+
- parsers/libtree-sitter-objc-linux-x86_64.so
|
201
224
|
- parsers/libtree-sitter-swift-darwin-arm64.dylib
|
202
225
|
- parsers/libtree-sitter-swift-linux-x86_64.so
|
203
226
|
homepage: https://github.com/EmergeTools/emerge-cli
|
@@ -208,7 +231,7 @@ metadata:
|
|
208
231
|
source_code_uri: https://github.com/EmergeTools/emerge-cli
|
209
232
|
changelog_uri: https://github.com/EmergeTools/emerge-cli/blob/main/CHANGELOG.md
|
210
233
|
rubygems_mfa_required: 'true'
|
211
|
-
post_install_message:
|
234
|
+
post_install_message:
|
212
235
|
rdoc_options: []
|
213
236
|
require_paths:
|
214
237
|
- lib
|
@@ -224,7 +247,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
224
247
|
version: '0'
|
225
248
|
requirements: []
|
226
249
|
rubygems_version: 3.5.11
|
227
|
-
signing_key:
|
250
|
+
signing_key:
|
228
251
|
specification_version: 4
|
229
252
|
summary: Emerge CLI
|
230
253
|
test_files: []
|