emerge 0.1.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 +7 -0
- data/CHANGELOG.md +5 -0
- data/README.md +94 -0
- data/exe/emerge +6 -0
- data/lib/commands/config/snapshots/snapshots_ios.rb +235 -0
- data/lib/commands/global_options.rb +15 -0
- data/lib/commands/integrate/fastlane.rb +124 -0
- data/lib/commands/upload/snapshots/client_libraries/default.rb +26 -0
- data/lib/commands/upload/snapshots/client_libraries/paparazzi.rb +29 -0
- data/lib/commands/upload/snapshots/client_libraries/swift_snapshot_testing.rb +28 -0
- data/lib/commands/upload/snapshots/snapshots.rb +247 -0
- data/lib/emerge_cli.rb +37 -0
- data/lib/utils/git.rb +103 -0
- data/lib/utils/git_info_provider.rb +25 -0
- data/lib/utils/git_result.rb +14 -0
- data/lib/utils/github.rb +71 -0
- data/lib/utils/logger.rb +60 -0
- data/lib/utils/network.rb +95 -0
- data/lib/utils/profiler.rb +49 -0
- data/lib/utils/project_detector.rb +11 -0
- data/lib/version.rb +3 -0
- metadata +277 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 8b51ab2fab1683825f19ebb9be0eb07b7901cb49d194adcfcb3488ebf17440dd
|
4
|
+
data.tar.gz: cb8d2ead013d712c99e5213a7d4d39cac1b2ad67afa0d51aaed3b06e86a43605
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1360c8d0acde63b549d69c23468fdd24b7c4a37e399988faec47e88060d9edf7e9abf945657f6deaa4af052c9b9cc307b6f4cd2c5eefe741bc3e9cce4dbff1b0
|
7
|
+
data.tar.gz: 7c515b04f02ef2c28e9452f9edeb36d62bf4754fe52c0a78b727b202c233a1291d418cab3d341e2eab527afd7fd5ffc77726b2e7af85c46693538fc273cfd3c6
|
data/CHANGELOG.md
ADDED
data/README.md
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
# Emerge CLI
|
2
|
+
|
3
|
+
The official CLI for Emerge Tools.
|
4
|
+
|
5
|
+
[Emerge](https://emergetools.com) offers a suite of products to help optimize app size, performance, and quality by detecting regressions before they make it to production. This plugin provides a set of actions to interact with the Emerge API.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
This tool is packaged as a Ruby Gem which can be installed by:
|
10
|
+
|
11
|
+
```
|
12
|
+
gem install emerge
|
13
|
+
```
|
14
|
+
|
15
|
+
## API Key
|
16
|
+
|
17
|
+
Follow our guide to obtain an [API key](https://docs.emergetools.com/docs/uploading-basics#obtain-an-api-key) for your organization. The API Token is used by the CLI to authenticate with the Emerge API. The CLI will automatically pick up the API key if configured as an `EMERGE_API_TOKEN` environment variable, or you can manually pass it into individual commands.
|
18
|
+
|
19
|
+
## Snapshots
|
20
|
+
|
21
|
+
Uploads a directory of images to be used in [Emerge Snapshot Testing](https://docs.emergetools.com/docs/snapshot-testing).
|
22
|
+
|
23
|
+
Run `emerge upload snapshots -h` for a full list of supported options.
|
24
|
+
|
25
|
+
Example:
|
26
|
+
|
27
|
+
```shell
|
28
|
+
emerge upload snapshots \
|
29
|
+
--name "AwesomeApp" \
|
30
|
+
--id "com.emerge.awesomeapp" \
|
31
|
+
--repo-name "EmergeTools/AwesomeApp" \
|
32
|
+
path/to/snapshot/images
|
33
|
+
```
|
34
|
+
|
35
|
+
### Git Configuration
|
36
|
+
|
37
|
+
For CI diffs to work, Emerge needs the appropriate Git `sha` and `base_sha` values set on each build. Emerge will automatically compare a build at `sha` against the build we find matching the `base_sha` for a given application id. We also recommend setting `pr_number`, `branch`, and `repo_name` for the best experience.
|
38
|
+
|
39
|
+
For example:
|
40
|
+
|
41
|
+
- `sha`: `pr-branch-commit-1`
|
42
|
+
- `base_sha`: `main-branch-commit-1`
|
43
|
+
- `pr_number`: `42`
|
44
|
+
- `branch`: `my-awesome-feature`
|
45
|
+
- `repo_name`: `EmergeTools/hackernews`
|
46
|
+
|
47
|
+
Will compare the snapshot diffs of your pull request changes.
|
48
|
+
|
49
|
+
This plugin will automatically configure Git values for you assuming certain Github workflow triggers:
|
50
|
+
|
51
|
+
```yaml
|
52
|
+
on:
|
53
|
+
# Produce base builds with a 'sha' when commits are pushed to the main branch
|
54
|
+
push:
|
55
|
+
branches: [main]
|
56
|
+
|
57
|
+
# Produce branch comparison builds with `sha` and `base_sha` when commits are pushed
|
58
|
+
# to open pull requests
|
59
|
+
pull_request:
|
60
|
+
branches: [main]
|
61
|
+
...
|
62
|
+
```
|
63
|
+
|
64
|
+
If this doesn't cover your use-case, manually set the `sha` and `base_sha` values when calling the Emerge plugin.
|
65
|
+
|
66
|
+
### Using with swift-snapshot-testing
|
67
|
+
|
68
|
+
Snapshots generated via [swift-snapshot-testing](https://github.com/pointfreeco/swift-snapshot-testing) are natively supported by the CLI by setting `--client-library swift-snapshot-testing` and a `--project-root` directory. This will scan your project for all images found in `__Snapshot__` directories.
|
69
|
+
|
70
|
+
Example:
|
71
|
+
|
72
|
+
```shell
|
73
|
+
emerge upload snapshots \
|
74
|
+
--name "AwesomeApp swift-snapshot-testing" \
|
75
|
+
--id "com.emerge.awesomeapp.swift-snapshot-testing" \
|
76
|
+
--repo-name "EmergeTools/AwesomeApp" \
|
77
|
+
--client-library swift-snapshot-testing \
|
78
|
+
--project-root /my/awesomeapp/ios/repo
|
79
|
+
```
|
80
|
+
|
81
|
+
### Using with Paparazzi
|
82
|
+
|
83
|
+
Snapshots generated via [Paparazzi](https://github.com/cashapp/paparazzi) are natively supported by the CLI by setting `--client-library paparazzi` and a `--project-root` directory. This will scan your project for all images found in `src/test/snapshots/images` directories.
|
84
|
+
|
85
|
+
Example:
|
86
|
+
|
87
|
+
```shell
|
88
|
+
emerge upload snapshots \
|
89
|
+
--name "AwesomeApp Paparazzi" \
|
90
|
+
--id "com.emerge.awesomeapp.paparazzi" \
|
91
|
+
--repo-name "EmergeTools/AwesomeApp" \
|
92
|
+
--client-library paparazzi \
|
93
|
+
--project-root /my/awesomeapp/android/repo
|
94
|
+
```
|
data/exe/emerge
ADDED
@@ -0,0 +1,235 @@
|
|
1
|
+
require 'dry/cli'
|
2
|
+
require 'json'
|
3
|
+
require 'uri'
|
4
|
+
require 'chunky_png'
|
5
|
+
require 'async'
|
6
|
+
require 'async/barrier'
|
7
|
+
require 'async/semaphore'
|
8
|
+
require 'async/http/internet/instance'
|
9
|
+
require 'yaml'
|
10
|
+
|
11
|
+
require 'tty-prompt'
|
12
|
+
require 'tty-table'
|
13
|
+
|
14
|
+
module EmergeCLI
|
15
|
+
module Commands
|
16
|
+
module Config
|
17
|
+
class SnapshotsIOS < EmergeCLI::Commands::GlobalOptions
|
18
|
+
desc 'Configure snapshot testing for iOS'
|
19
|
+
|
20
|
+
# Optional options
|
21
|
+
option :interactive, type: :boolean, required: false,
|
22
|
+
desc: 'Run interactively'
|
23
|
+
option :clear, type: :boolean, required: false, desc: 'Clear existing configuration'
|
24
|
+
option :os_version, type: :string, required: true, desc: 'OS version'
|
25
|
+
option :launch_arguments, type: :array, required: false, desc: 'Launch arguments to set'
|
26
|
+
option :env_variables, type: :array, required: false, desc: 'Environment variables to set'
|
27
|
+
option :exact_match_excluded_previews, type: :array, required: false, desc: 'Exact match excluded previews'
|
28
|
+
option :regex_excluded_previews, type: :array, required: false, desc: 'Regex excluded previews'
|
29
|
+
|
30
|
+
# Constants
|
31
|
+
EXCLUDED_PREVIEW_PROMPT = 'Do you want to exclude any previews by exact match?'.freeze
|
32
|
+
EXCLUDED_PREVIEW_FINISH_PROMPT = 'Enter the previews you want to exclude (leave blank to finish)'.freeze
|
33
|
+
EXCLUDED_REGEX_PREVIEW_PROMPT = 'Do you want to exclude any previews by regex?'.freeze
|
34
|
+
EXCLUDED_REGEX_PREVIEW_FINISH_PROMPT = 'Enter the previews you want to exclude (leave blank to finish)'.freeze
|
35
|
+
ARGUMENTS_PROMPT = 'Do you want to set any arguments?'.freeze
|
36
|
+
ARGUMENTS_FINISH_PROMPT = 'Enter the argument you want to set (leave blank to finish)'.freeze
|
37
|
+
ENV_VARIABLES_PROMPT = 'Do you want to set any environment variables?'.freeze
|
38
|
+
ENV_VARIABLES_FINISH_PROMPT = "Enter the environment variable you want to set (leave blank to finish) with \
|
39
|
+
format KEY=VALUE".freeze
|
40
|
+
AVAILABLE_OS_VERSIONS = ['17.2', '17.5', '18.0'].freeze
|
41
|
+
|
42
|
+
def initialize; end
|
43
|
+
|
44
|
+
def call(**options)
|
45
|
+
@options = options
|
46
|
+
before(options)
|
47
|
+
|
48
|
+
Sync do
|
49
|
+
validate_options
|
50
|
+
|
51
|
+
run_interactive_mode if @options[:interactive]
|
52
|
+
|
53
|
+
run_non_interactive_mode if !@options[:interactive]
|
54
|
+
|
55
|
+
Logger.warn 'Remember to copy `emerge_config.yml` to your project XCArchive before uploading it!'
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def validate_options
|
62
|
+
if @options[:interactive] && (!@options[:os_version].nil? || !@options[:launch_arguments].nil? ||
|
63
|
+
!@options[:env_variables].nil? || !@options[:exact_match_excluded_previews].nil? ||
|
64
|
+
!@options[:regex_excluded_previews].nil?)
|
65
|
+
Logger.warn 'All options are ignored when using interactive mode'
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def run_interactive_mode
|
70
|
+
prompt = TTY::Prompt.new
|
71
|
+
|
72
|
+
override_config = false
|
73
|
+
if File.exist?('emerge_config.yml')
|
74
|
+
Logger.warn 'There is already a emerge_config.yml file.'
|
75
|
+
prompt.yes?('Do you want to overwrite it?', default: false) do |answer|
|
76
|
+
override_config = true if answer
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
if !override_config && File.exist?('emerge_config.yml')
|
81
|
+
config = YAML.load_file('emerge_config.yml')
|
82
|
+
config['snapshots']['ios']['runSettings'] = []
|
83
|
+
else
|
84
|
+
config = {
|
85
|
+
'version' => 2.0,
|
86
|
+
'snapshots' => {
|
87
|
+
'ios' => {
|
88
|
+
'runSettings' => []
|
89
|
+
}
|
90
|
+
}
|
91
|
+
}
|
92
|
+
end
|
93
|
+
|
94
|
+
Logger.info 'Creating a new config file'
|
95
|
+
|
96
|
+
end_config = false
|
97
|
+
loop do
|
98
|
+
os_version = get_os_version(prompt)
|
99
|
+
|
100
|
+
excluded_previews = get_array_from_user(prompt, EXCLUDED_PREVIEW_PROMPT, EXCLUDED_PREVIEW_FINISH_PROMPT)
|
101
|
+
excluded_regex_previews = get_array_from_user(prompt, EXCLUDED_REGEX_PREVIEW_PROMPT,
|
102
|
+
EXCLUDED_REGEX_PREVIEW_FINISH_PROMPT)
|
103
|
+
arguments_array = get_array_from_user(prompt, ARGUMENTS_PROMPT, ARGUMENTS_FINISH_PROMPT)
|
104
|
+
env_variables_array = get_array_from_user(prompt, ENV_VARIABLES_PROMPT, ENV_VARIABLES_FINISH_PROMPT)
|
105
|
+
|
106
|
+
excluded = get_parsed_previews(excluded_previews, excluded_regex_previews)
|
107
|
+
env_variables = get_parsed_env_variables(env_variables_array)
|
108
|
+
|
109
|
+
os_settings = {
|
110
|
+
'osVersion' => os_version,
|
111
|
+
'excludedPreviews' => excluded,
|
112
|
+
'envVariables' => env_variables,
|
113
|
+
'arguments' => arguments_array
|
114
|
+
}
|
115
|
+
show_config(os_settings)
|
116
|
+
save = prompt.yes?('Do you want to save this setting?')
|
117
|
+
config['snapshots']['ios']['runSettings'].push(os_settings) if save
|
118
|
+
|
119
|
+
end_config = !prompt.yes?('Do you want to continue adding more settings?')
|
120
|
+
break if end_config
|
121
|
+
end
|
122
|
+
|
123
|
+
File.write('emerge_config.yml', config.to_yaml)
|
124
|
+
Logger.info 'Configuration file created successfully!'
|
125
|
+
end
|
126
|
+
|
127
|
+
def run_non_interactive_mode
|
128
|
+
config = {}
|
129
|
+
if File.exist?('emerge_config.yml')
|
130
|
+
config = YAML.load_file('emerge_config.yml')
|
131
|
+
if !@options[:clear] && !config['snapshots'].nil? && !config['snapshots']['ios'].nil? &&
|
132
|
+
!config['snapshots']['ios']['runSettings'].nil?
|
133
|
+
raise 'There is already a configuration file with settings. Use the --clear flag to overwrite it.'
|
134
|
+
end
|
135
|
+
|
136
|
+
config['snapshots']['ios']['runSettings'] = []
|
137
|
+
|
138
|
+
else
|
139
|
+
config = {
|
140
|
+
'version' => 2.0,
|
141
|
+
'snapshots' => {
|
142
|
+
'ios' => {
|
143
|
+
'runSettings' => []
|
144
|
+
}
|
145
|
+
}
|
146
|
+
}
|
147
|
+
end
|
148
|
+
|
149
|
+
excluded_previews = get_parsed_previews(@options[:exact_match_excluded_previews] || [],
|
150
|
+
@options[:regex_excluded_previews] || [])
|
151
|
+
env_variables = get_parsed_env_variables(@options[:env_variables] || [])
|
152
|
+
|
153
|
+
os_version = @options[:os_version]
|
154
|
+
if os_version.nil?
|
155
|
+
Logger.warn 'No OS version was provided, defaulting to 17.5'
|
156
|
+
os_version = '17.5'
|
157
|
+
end
|
158
|
+
|
159
|
+
os_settings = {
|
160
|
+
'osVersion' => os_version,
|
161
|
+
'excludedPreviews' => excluded_previews,
|
162
|
+
'envVariables' => env_variables,
|
163
|
+
'arguments' => @options[:launch_arguments] || []
|
164
|
+
}
|
165
|
+
config['snapshots']['ios']['runSettings'].push(os_settings)
|
166
|
+
File.write('emerge_config.yml', config.to_yaml)
|
167
|
+
Logger.info 'Configuration file created successfully!'
|
168
|
+
show_config(os_settings)
|
169
|
+
end
|
170
|
+
|
171
|
+
def get_os_version(prompt)
|
172
|
+
os_version = prompt.select('Select the OS version you want to run the tests on') do |answer|
|
173
|
+
AVAILABLE_OS_VERSIONS.each do |version|
|
174
|
+
answer.choice version, version.to_f
|
175
|
+
end
|
176
|
+
answer.choice 'Custom', 'custom'
|
177
|
+
end
|
178
|
+
os_version = prompt.ask('Enter the OS version you want to run the tests on') if os_version == 'custom'
|
179
|
+
os_version
|
180
|
+
end
|
181
|
+
|
182
|
+
def get_array_from_user(prompt, first_prompt_message, second_prompt_message)
|
183
|
+
continue = prompt.yes?(first_prompt_message)
|
184
|
+
return [] if !continue
|
185
|
+
array = []
|
186
|
+
loop do
|
187
|
+
item = prompt.ask(second_prompt_message)
|
188
|
+
if item == '' || item.nil?
|
189
|
+
continue = false
|
190
|
+
else
|
191
|
+
array.push(item)
|
192
|
+
end
|
193
|
+
break unless continue
|
194
|
+
end
|
195
|
+
array
|
196
|
+
end
|
197
|
+
|
198
|
+
def show_config(config)
|
199
|
+
table = TTY::Table.new(
|
200
|
+
header: %w[Key Value],
|
201
|
+
rows: config.to_a
|
202
|
+
)
|
203
|
+
puts table.render(:ascii)
|
204
|
+
end
|
205
|
+
|
206
|
+
def get_parsed_previews(previews_exact, previews_regex)
|
207
|
+
excluded = []
|
208
|
+
previews_exact.each do |preview|
|
209
|
+
excluded.push({
|
210
|
+
'type' => 'exact',
|
211
|
+
'value' => preview
|
212
|
+
})
|
213
|
+
end
|
214
|
+
previews_regex.each do |preview|
|
215
|
+
excluded.push({
|
216
|
+
'type' => 'regex',
|
217
|
+
'value' => preview
|
218
|
+
})
|
219
|
+
end
|
220
|
+
excluded
|
221
|
+
end
|
222
|
+
|
223
|
+
def get_parsed_env_variables(env_variables)
|
224
|
+
env_variables_array_fixed = []
|
225
|
+
env_variables.each do |env_variable|
|
226
|
+
key, value = env_variable.split('=')
|
227
|
+
env_variables_array_fixed.push({
|
228
|
+
'key' => key, 'value' => value
|
229
|
+
})
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'dry/cli'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
module EmergeCLI
|
5
|
+
module Commands
|
6
|
+
class GlobalOptions < Dry::CLI::Command
|
7
|
+
option :debug, type: :boolean, default: false, desc: 'Enable debug logging'
|
8
|
+
|
9
|
+
def before(args)
|
10
|
+
log_level = args[:debug] ? ::Logger::DEBUG : ::Logger::INFO
|
11
|
+
EmergeCLI::Logger.configure(log_level)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'dry/cli'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module EmergeCLI
|
5
|
+
module Commands
|
6
|
+
module Integrate
|
7
|
+
class Fastlane < EmergeCLI::Commands::GlobalOptions
|
8
|
+
desc 'Integrate Emerge into your iOS project via Fastlane'
|
9
|
+
|
10
|
+
argument :path, type: :string, required: false, default: '.',
|
11
|
+
desc: 'Project path (defaults to current directory)'
|
12
|
+
|
13
|
+
def call(path: '.', **_options)
|
14
|
+
@project_path = File.expand_path(path)
|
15
|
+
Logger.info "Project path: #{@project_path}"
|
16
|
+
|
17
|
+
Logger.info '🔍 Detecting project type...'
|
18
|
+
detector = ProjectDetector.new(@project_path)
|
19
|
+
|
20
|
+
if detector.ios_project?
|
21
|
+
Logger.info '📱 iOS project detected!'
|
22
|
+
setup_ios
|
23
|
+
else
|
24
|
+
Logger.error "❌ Error: Could not detect project. Make sure you're in the root directory of an iOS project."
|
25
|
+
exit 1
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def setup_ios
|
32
|
+
Logger.info 'Setting up Emerge Tools for iOS project using Fastlane...'
|
33
|
+
|
34
|
+
setup_gemfile
|
35
|
+
setup_fastfile
|
36
|
+
|
37
|
+
# Install Emerge Fastlane plugin
|
38
|
+
Logger.info 'Installing Emerge Fastlane plugin...'
|
39
|
+
system('fastlane add_plugin emerge')
|
40
|
+
|
41
|
+
print_ios_completion_message
|
42
|
+
end
|
43
|
+
|
44
|
+
def setup_gemfile
|
45
|
+
gemfile_path = File.join(@project_path, 'Gemfile')
|
46
|
+
if File.exist?(gemfile_path)
|
47
|
+
Logger.info 'Updating existing Gemfile...'
|
48
|
+
current_content = File.read(gemfile_path)
|
49
|
+
current_content << "\ngem 'fastlane'" unless current_content.include?('gem "fastlane"')
|
50
|
+
current_content << "\ngem 'xcpretty'" unless current_content.include?('gem "xcpretty"')
|
51
|
+
File.write(gemfile_path, current_content)
|
52
|
+
else
|
53
|
+
Logger.error 'No Gemfile found. Please follow the Fastlane setup instructions before running this.'
|
54
|
+
exit 1
|
55
|
+
end
|
56
|
+
|
57
|
+
Logger.info 'Installing gems...'
|
58
|
+
system('bundle install')
|
59
|
+
end
|
60
|
+
|
61
|
+
def setup_fastfile
|
62
|
+
fastfile_dir = File.join(@project_path, 'fastlane')
|
63
|
+
FileUtils.mkdir_p(fastfile_dir)
|
64
|
+
fastfile_path = File.join(fastfile_dir, 'Fastfile')
|
65
|
+
|
66
|
+
if File.exist?(fastfile_path)
|
67
|
+
Logger.info 'Updating existing Fastfile...'
|
68
|
+
update_existing_fastfile(fastfile_path)
|
69
|
+
else
|
70
|
+
Logger.error 'No Fastfile found. Please follow the Fastlane setup instructions before running this.'
|
71
|
+
exit 1
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def update_existing_fastfile(fastfile_path)
|
76
|
+
current_content = File.read(fastfile_path)
|
77
|
+
|
78
|
+
# Add platform :ios block if not present
|
79
|
+
current_content += "\nplatform :ios do\nend\n" unless current_content.match?(/platform\s+:ios\s+do/)
|
80
|
+
|
81
|
+
# Add app_size lane if not present
|
82
|
+
unless current_content.match?(/^\s*lane\s*:app_size\s*do/)
|
83
|
+
app_size_lane = <<~'RUBY'.gsub(/^/, ' ')
|
84
|
+
lane :app_size do
|
85
|
+
# NOTE: If you already have a lane setup to build your app, then you can that instead of this and call emerge() after it.
|
86
|
+
build_app(scheme: ENV["SCHEME_NAME"], export_method: "development")
|
87
|
+
emerge(tag: ENV['EMERGE_BUILD_TYPE'] || "default")
|
88
|
+
end
|
89
|
+
RUBY
|
90
|
+
current_content.sub!(/platform\s+:ios\s+do.*$/) { |match| "#{match}\n#{app_size_lane}" }
|
91
|
+
end
|
92
|
+
|
93
|
+
# Add snapshots lane if not present
|
94
|
+
unless current_content.match?(/^\s*lane\s*:build_upload_emerge_snapshot\s*do/)
|
95
|
+
snapshot_lane = <<~'RUBY'.gsub(/^/, ' ')
|
96
|
+
desc 'Build and upload snapshot build to Emerge Tools'
|
97
|
+
lane :build_upload_emerge_snapshot do
|
98
|
+
emerge_snapshot(scheme: ENV["SCHEME_NAME"])
|
99
|
+
end
|
100
|
+
RUBY
|
101
|
+
current_content.sub!(/lane\s+:app_size\s+do.*?end/m) { |match| "#{match}\n\n#{snapshot_lane}" }
|
102
|
+
end
|
103
|
+
|
104
|
+
# Clean up any multiple blank lines
|
105
|
+
current_content.gsub!(/\n{3,}/, "\n\n")
|
106
|
+
|
107
|
+
File.write(fastfile_path, current_content)
|
108
|
+
end
|
109
|
+
|
110
|
+
def command_exists?(command)
|
111
|
+
system("which #{command} > /dev/null 2>&1")
|
112
|
+
end
|
113
|
+
|
114
|
+
def print_ios_completion_message
|
115
|
+
Logger.info "✅ iOS setup complete! Don't forget to:"
|
116
|
+
Logger.info '1. Set your EMERGE_API_TOKEN environment variable (both locally and in your CI/CD pipeline)'
|
117
|
+
Logger.info '2. Set your SCHEME_NAME environment variable'
|
118
|
+
Logger.info "3. Run 'fastlane app_size' to analyze your app"
|
119
|
+
Logger.info "4. Run 'fastlane build_upload_emerge_snapshot' to analyze your snapshots"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module EmergeCLI
|
2
|
+
module Commands
|
3
|
+
module Upload
|
4
|
+
module ClientLibraries
|
5
|
+
class Default
|
6
|
+
def initialize(image_paths)
|
7
|
+
@image_paths = image_paths
|
8
|
+
end
|
9
|
+
|
10
|
+
def image_files
|
11
|
+
@image_paths.flat_map { |path| Dir.glob("#{path}/**/*.png") }
|
12
|
+
end
|
13
|
+
|
14
|
+
def parse_file_info(image_path)
|
15
|
+
file_name = File.basename(image_path)
|
16
|
+
{
|
17
|
+
file_name:,
|
18
|
+
group_name: File.basename(image_path, '.*'),
|
19
|
+
variant_name: nil
|
20
|
+
}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module EmergeCLI
|
2
|
+
module Commands
|
3
|
+
module Upload
|
4
|
+
module ClientLibraries
|
5
|
+
class Paparazzi
|
6
|
+
def initialize(project_root)
|
7
|
+
@project_root = project_root
|
8
|
+
end
|
9
|
+
|
10
|
+
def image_files
|
11
|
+
# TODO: support "paparazzi.snapshot.dir" dynamic config
|
12
|
+
Dir.glob(File.join(@project_root, '**/src/test/snapshots/images/**/*.png'))
|
13
|
+
end
|
14
|
+
|
15
|
+
def parse_file_info(image_path)
|
16
|
+
file_name = image_path.split('src/test/snapshots/images/').last
|
17
|
+
test_class_name = File.basename(File.dirname(image_path))
|
18
|
+
|
19
|
+
{
|
20
|
+
file_name:,
|
21
|
+
group_name: test_class_name, # TODO: add support for nicer group names
|
22
|
+
variant_name: File.basename(file_name, '.*')
|
23
|
+
}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module EmergeCLI
|
2
|
+
module Commands
|
3
|
+
module Upload
|
4
|
+
module ClientLibraries
|
5
|
+
class SwiftSnapshotTesting
|
6
|
+
def initialize(project_root)
|
7
|
+
@project_root = project_root
|
8
|
+
end
|
9
|
+
|
10
|
+
def image_files
|
11
|
+
Dir.glob(File.join(@project_root, '**/__Snapshots__/**/*.png'))
|
12
|
+
end
|
13
|
+
|
14
|
+
def parse_file_info(image_path)
|
15
|
+
file_name = image_path.split('__Snapshots__/').last
|
16
|
+
test_class_name = File.basename(File.dirname(image_path))
|
17
|
+
|
18
|
+
{
|
19
|
+
file_name:,
|
20
|
+
group_name: test_class_name.sub(/Tests$/, ''),
|
21
|
+
variant_name: File.basename(file_name, '.*')
|
22
|
+
}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|