ruby_raider 1.1.4 → 2.0.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/.github/workflows/integration.yml +4 -6
- data/.github/workflows/reek.yml +6 -5
- data/.github/workflows/release.yml +175 -0
- data/.github/workflows/rubocop.yml +7 -6
- data/.github/workflows/system_tests.yml +83 -0
- data/.gitignore +1 -1
- data/.rubocop.yml +24 -0
- data/README.md +3 -1
- data/RELEASE.md +412 -0
- data/RELEASE_QUICK_GUIDE.md +77 -0
- data/bin/release +186 -0
- data/lib/adopter/adopt_menu.rb +150 -0
- data/lib/adopter/converters/base_converter.rb +85 -0
- data/lib/adopter/converters/identity_converter.rb +56 -0
- data/lib/adopter/migration_plan.rb +75 -0
- data/lib/adopter/migrator.rb +96 -0
- data/lib/adopter/plan_builder.rb +278 -0
- data/lib/adopter/project_analyzer.rb +256 -0
- data/lib/adopter/project_detector.rb +159 -0
- data/lib/commands/adopt_commands.rb +43 -0
- data/lib/generators/automation/templates/account.tt +9 -5
- data/lib/generators/automation/templates/appium_caps.tt +60 -6
- data/lib/generators/automation/templates/home.tt +4 -4
- data/lib/generators/automation/templates/login.tt +61 -4
- data/lib/generators/automation/templates/page.tt +13 -7
- data/lib/generators/automation/templates/partials/home_page_selector.tt +4 -4
- data/lib/generators/automation/templates/partials/initialize_selector.tt +3 -1
- data/lib/generators/automation/templates/partials/pdp_page_selector.tt +4 -4
- data/lib/generators/automation/templates/partials/visit_method.tt +11 -1
- data/lib/generators/automation/templates/pdp.tt +1 -1
- data/lib/generators/cucumber/templates/env.tt +6 -4
- data/lib/generators/cucumber/templates/partials/capybara_env.tt +20 -0
- data/lib/generators/cucumber/templates/partials/capybara_world.tt +6 -0
- data/lib/generators/cucumber/templates/partials/mobile_steps.tt +2 -2
- data/lib/generators/cucumber/templates/partials/web_steps.tt +4 -3
- data/lib/generators/cucumber/templates/steps.tt +2 -2
- data/lib/generators/cucumber/templates/world.tt +5 -3
- data/lib/generators/generator.rb +14 -2
- data/lib/generators/helper_generator.rb +16 -3
- data/lib/generators/infrastructure/github_generator.rb +6 -0
- data/lib/generators/infrastructure/templates/github.tt +11 -7
- data/lib/generators/infrastructure/templates/github_appium.tt +108 -0
- data/lib/generators/infrastructure/templates/gitlab.tt +5 -2
- data/lib/generators/invoke_generators.rb +1 -0
- data/lib/generators/menu_generator.rb +2 -0
- data/lib/generators/minitest/minitest_generator.rb +23 -0
- data/lib/generators/minitest/templates/test.tt +93 -0
- data/lib/generators/rspec/templates/spec.tt +12 -10
- data/lib/generators/template_renderer/partial_cache.rb +116 -0
- data/lib/generators/template_renderer/partial_resolver.rb +103 -0
- data/lib/generators/template_renderer/template_error.rb +50 -0
- data/lib/generators/template_renderer.rb +90 -0
- data/lib/generators/templates/common/config.tt +2 -2
- data/lib/generators/templates/common/gemfile.tt +15 -3
- data/lib/generators/templates/common/partials/web_config.tt +1 -1
- data/lib/generators/templates/common/read_me.tt +3 -1
- data/lib/generators/templates/helpers/allure_helper.tt +2 -2
- data/lib/generators/templates/helpers/browser_helper.tt +1 -0
- data/lib/generators/templates/helpers/capybara_helper.tt +28 -0
- data/lib/generators/templates/helpers/driver_helper.tt +1 -1
- data/lib/generators/templates/helpers/partials/allure_imports.tt +3 -1
- data/lib/generators/templates/helpers/partials/allure_requirements.tt +3 -1
- data/lib/generators/templates/helpers/partials/appium_driver.tt +46 -0
- data/lib/generators/templates/helpers/partials/axe_driver.tt +10 -0
- data/lib/generators/templates/helpers/partials/browserstack_config.tt +13 -0
- data/lib/generators/templates/helpers/partials/driver_and_options.tt +6 -114
- data/lib/generators/templates/helpers/partials/quit_driver.tt +3 -1
- data/lib/generators/templates/helpers/partials/screenshot.tt +3 -1
- data/lib/generators/templates/helpers/partials/selenium_driver.tt +25 -0
- data/lib/generators/templates/helpers/spec_helper.tt +17 -4
- data/lib/generators/templates/helpers/test_helper.tt +26 -0
- data/lib/generators/templates/helpers/visual_spec_helper.tt +1 -1
- data/lib/ruby_raider.rb +5 -0
- data/lib/version +1 -1
- data/spec/adopter/adopt_menu_spec.rb +176 -0
- data/spec/adopter/converters/identity_converter_spec.rb +145 -0
- data/spec/adopter/migration_plan_spec.rb +113 -0
- data/spec/adopter/migrator_spec.rb +277 -0
- data/spec/adopter/plan_builder_spec.rb +298 -0
- data/spec/adopter/project_analyzer_spec.rb +337 -0
- data/spec/adopter/project_detector_spec.rb +295 -0
- data/spec/generators/fixtures/templates/test.tt +1 -0
- data/spec/generators/fixtures/templates/test_partial.tt +1 -0
- data/spec/generators/template_renderer_spec.rb +298 -0
- data/spec/integration/commands/scaffolding_commands_spec.rb +2 -2
- data/spec/integration/commands/utility_commands_spec.rb +2 -2
- data/spec/integration/end_to_end_spec.rb +325 -0
- data/spec/integration/generators/automation_generator_spec.rb +11 -11
- data/spec/integration/generators/common_generator_spec.rb +40 -40
- data/spec/integration/generators/cucumber_generator_spec.rb +7 -7
- data/spec/integration/generators/github_generator_spec.rb +8 -8
- data/spec/integration/generators/gitlab_generator_spec.rb +8 -8
- data/spec/integration/generators/helpers_generator_spec.rb +73 -35
- data/spec/integration/generators/minitest_generator_spec.rb +70 -0
- data/spec/integration/generators/rspec_generator_spec.rb +7 -7
- data/spec/integration/settings_helper.rb +1 -1
- data/spec/integration/spec_helper.rb +20 -2
- data/spec/system/capybara_spec.rb +42 -0
- data/spec/system/selenium_spec.rb +19 -17
- data/spec/system/support/system_test_helper.rb +35 -0
- data/spec/system/watir_spec.rb +19 -17
- metadata +46 -16
- data/.github/workflows/push_gem.yml +0 -37
- data/.github/workflows/selenium.yml +0 -22
- data/.github/workflows/watir.yml +0 -22
- data/lib/generators/automation/templates/partials/android_caps.tt +0 -17
- data/lib/generators/automation/templates/partials/cross_platform_caps.tt +0 -25
- data/lib/generators/automation/templates/partials/ios_caps.tt +0 -18
- data/lib/generators/automation/templates/partials/selenium_account.tt +0 -9
- data/lib/generators/automation/templates/partials/selenium_login.tt +0 -34
- data/lib/generators/automation/templates/partials/watir_account.tt +0 -7
- data/lib/generators/automation/templates/partials/watir_login.tt +0 -32
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'tty-prompt'
|
|
4
|
+
require_relative 'project_analyzer'
|
|
5
|
+
require_relative 'plan_builder'
|
|
6
|
+
require_relative 'migrator'
|
|
7
|
+
|
|
8
|
+
module Adopter
|
|
9
|
+
# :reek:TooManyMethods { enabled: false }
|
|
10
|
+
class AdoptMenu
|
|
11
|
+
WEB_AUTOMATIONS = %w[selenium capybara watir].freeze
|
|
12
|
+
TEST_FRAMEWORKS = %w[rspec cucumber minitest].freeze
|
|
13
|
+
CI_PLATFORMS = %w[github gitlab].freeze
|
|
14
|
+
|
|
15
|
+
def initialize
|
|
16
|
+
@prompt = TTY::Prompt.new
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def run
|
|
20
|
+
source_path = ask_source_path
|
|
21
|
+
detection = preview_detection(source_path)
|
|
22
|
+
target_automation = ask_target_automation(detection[:automation])
|
|
23
|
+
target_framework = ask_target_framework(detection[:framework])
|
|
24
|
+
output_path = ask_output_path
|
|
25
|
+
ci_platform = ask_ci_platform(detection[:ci_platform])
|
|
26
|
+
|
|
27
|
+
execute_adoption(
|
|
28
|
+
source_path: source_path,
|
|
29
|
+
output_path: output_path,
|
|
30
|
+
target_automation: target_automation,
|
|
31
|
+
target_framework: target_framework,
|
|
32
|
+
ci_platform: ci_platform
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Programmatic entry point for raider_desktop and CLI --parameters
|
|
37
|
+
# :reek:LongParameterList { enabled: false }
|
|
38
|
+
def self.adopt(params)
|
|
39
|
+
validate_params!(params)
|
|
40
|
+
|
|
41
|
+
analysis = ProjectAnalyzer.new(params[:source_path]).analyze
|
|
42
|
+
plan = PlanBuilder.new(analysis, params).build
|
|
43
|
+
results = Migrator.new(plan).execute
|
|
44
|
+
|
|
45
|
+
{ plan: plan, results: results }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.validate_params!(params)
|
|
49
|
+
raise ArgumentError, 'source_path is required' unless params[:source_path]
|
|
50
|
+
raise ArgumentError, 'output_path is required' unless params[:output_path]
|
|
51
|
+
raise ArgumentError, 'target_automation is required' unless params[:target_automation]
|
|
52
|
+
raise ArgumentError, 'target_framework is required' unless params[:target_framework]
|
|
53
|
+
|
|
54
|
+
unless WEB_AUTOMATIONS.include?(params[:target_automation])
|
|
55
|
+
raise ArgumentError, "target_automation must be one of: #{WEB_AUTOMATIONS.join(', ')}"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
return if TEST_FRAMEWORKS.include?(params[:target_framework])
|
|
59
|
+
|
|
60
|
+
raise ArgumentError, "target_framework must be one of: #{TEST_FRAMEWORKS.join(', ')}"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def ask_source_path
|
|
66
|
+
path = @prompt.ask('Enter the path to your existing test project:') do |q|
|
|
67
|
+
q.required true
|
|
68
|
+
q.validate ->(input) { Dir.exist?(input) }
|
|
69
|
+
q.messages[:valid?] = 'Directory does not exist: %{value}'
|
|
70
|
+
end
|
|
71
|
+
File.expand_path(path)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def preview_detection(source_path)
|
|
75
|
+
detection = ProjectDetector.detect(source_path)
|
|
76
|
+
@prompt.say("\nDetected project settings:")
|
|
77
|
+
@prompt.say(" Automation: #{detection[:automation] || 'unknown'}")
|
|
78
|
+
@prompt.say(" Framework: #{detection[:framework] || 'unknown'}")
|
|
79
|
+
@prompt.say(" Browser: #{detection[:browser] || 'not detected'}")
|
|
80
|
+
@prompt.say(" URL: #{detection[:url] || 'not detected'}")
|
|
81
|
+
@prompt.say(" CI: #{detection[:ci_platform] || 'none'}")
|
|
82
|
+
@prompt.say('')
|
|
83
|
+
detection
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def ask_target_automation(detected)
|
|
87
|
+
default = WEB_AUTOMATIONS.include?(detected) ? detected : nil
|
|
88
|
+
@prompt.select('Select target automation framework:', WEB_AUTOMATIONS.map(&:capitalize),
|
|
89
|
+
default: default&.capitalize) do |menu|
|
|
90
|
+
menu.choice :Quit, -> { exit }
|
|
91
|
+
end.downcase
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def ask_target_framework(detected)
|
|
95
|
+
default = TEST_FRAMEWORKS.include?(detected) ? detected : nil
|
|
96
|
+
@prompt.select('Select target test framework:', TEST_FRAMEWORKS.map(&:capitalize),
|
|
97
|
+
default: default&.capitalize) do |menu|
|
|
98
|
+
menu.choice :Quit, -> { exit }
|
|
99
|
+
end.downcase
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def ask_output_path
|
|
103
|
+
@prompt.ask('Enter the output path for the new Raider project:') do |q|
|
|
104
|
+
q.required true
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def ask_ci_platform(detected)
|
|
109
|
+
choices = [{ name: 'Github Actions', value: 'github' },
|
|
110
|
+
{ name: 'Gitlab CI/CD', value: 'gitlab' },
|
|
111
|
+
{ name: 'No CI', value: nil }]
|
|
112
|
+
default = case detected
|
|
113
|
+
when 'github' then 'Github Actions'
|
|
114
|
+
when 'gitlab' then 'Gitlab CI/CD'
|
|
115
|
+
end
|
|
116
|
+
@prompt.select('Configure CI/CD?', choices, default: default)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def execute_adoption(params)
|
|
120
|
+
@prompt.say("\nAdopting project...")
|
|
121
|
+
result = self.class.adopt(params)
|
|
122
|
+
print_results(result[:plan], result[:results])
|
|
123
|
+
rescue MobileProjectError => e
|
|
124
|
+
@prompt.error(e.message)
|
|
125
|
+
rescue StandardError => e
|
|
126
|
+
@prompt.error("Adoption failed: #{e.message}")
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def print_results(plan, results)
|
|
130
|
+
@prompt.say("\nAdoption complete!")
|
|
131
|
+
@prompt.say(" Pages converted: #{results[:pages]}")
|
|
132
|
+
@prompt.say(" Tests converted: #{results[:tests]}")
|
|
133
|
+
@prompt.say(" Features copied: #{results[:features]}")
|
|
134
|
+
@prompt.say(" Steps converted: #{results[:steps]}")
|
|
135
|
+
|
|
136
|
+
unless results[:errors].empty?
|
|
137
|
+
@prompt.warn("\nErrors:")
|
|
138
|
+
results[:errors].each { |e| @prompt.warn(" - #{e}") }
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
unless plan.warnings.empty?
|
|
142
|
+
@prompt.warn("\nWarnings:")
|
|
143
|
+
plan.warnings.each { |w| @prompt.warn(" - #{w}") }
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
@prompt.say("\nOutput: #{plan.output_path}")
|
|
147
|
+
@prompt.say("Run: cd #{plan.output_path} && bundle install")
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Adopter
|
|
4
|
+
module Converters
|
|
5
|
+
class BaseConverter
|
|
6
|
+
RAIDER_BASE_CLASSES = %w[Page BasePage AbstractPage].freeze
|
|
7
|
+
|
|
8
|
+
def convert_page(content, _page_info)
|
|
9
|
+
content
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def convert_test(content, _test_info)
|
|
13
|
+
content
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def ensure_frozen_string_literal(content)
|
|
19
|
+
return content if content.include?('frozen_string_literal')
|
|
20
|
+
|
|
21
|
+
"# frozen_string_literal: true\n\n#{content}"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def update_base_class(content, target_base = 'Page')
|
|
25
|
+
content.gsub(/class\s+(\w+)\s*<\s*([\w:]+)/) do
|
|
26
|
+
class_name = ::Regexp.last_match(1)
|
|
27
|
+
current_base = ::Regexp.last_match(2)
|
|
28
|
+
if raider_compatible_base?(current_base)
|
|
29
|
+
"class #{class_name} < #{target_base}"
|
|
30
|
+
else
|
|
31
|
+
"class #{class_name} < #{current_base}"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def raider_compatible_base?(base_class)
|
|
37
|
+
RAIDER_BASE_CLASSES.include?(base_class) || base_class.include?('Page')
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def update_require_paths(content, target_automation)
|
|
41
|
+
result = content.dup
|
|
42
|
+
result = update_page_requires(result)
|
|
43
|
+
result = update_helper_requires(result, target_automation)
|
|
44
|
+
result
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def update_page_requires(content)
|
|
48
|
+
content.gsub(%r{require_relative\s+['"]\.*/(?:pages?|page_objects)/(\w+)['"]}) do
|
|
49
|
+
page_name = ::Regexp.last_match(1)
|
|
50
|
+
"require_relative '../page_objects/pages/#{page_name}'"
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def update_helper_requires(content, target_automation)
|
|
55
|
+
case target_automation
|
|
56
|
+
when 'capybara'
|
|
57
|
+
content.gsub(%r{require_relative\s+['"]\.*/(?:helpers?|support)/(?:driver|browser)_?\w*['"]},
|
|
58
|
+
"require_relative '../helpers/capybara_helper'")
|
|
59
|
+
when 'watir'
|
|
60
|
+
content.gsub(%r{require_relative\s+['"]\.*/(?:helpers?|support)/(?:driver|capybara)_?\w*['"]},
|
|
61
|
+
"require_relative '../helpers/browser_helper'")
|
|
62
|
+
else
|
|
63
|
+
content.gsub(%r{require_relative\s+['"]\.*/(?:helpers?|support)/(?:browser|capybara)_?\w*['"]},
|
|
64
|
+
"require_relative '../helpers/driver_helper'")
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def remove_driver_arg(content)
|
|
69
|
+
content.gsub(/(\w+)\.new\(\s*(?:driver|browser|page)\s*\)/, '\1.new')
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def swap_driver_arg(content, target_arg)
|
|
73
|
+
content.gsub(/(\w+)\.new\(\s*(?:driver|browser|page)\s*\)/, "\\1.new(#{target_arg})")
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def driver_arg_for(target_automation)
|
|
77
|
+
case target_automation
|
|
78
|
+
when 'watir' then 'browser'
|
|
79
|
+
when 'capybara' then nil
|
|
80
|
+
else 'driver'
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base_converter'
|
|
4
|
+
|
|
5
|
+
module Adopter
|
|
6
|
+
module Converters
|
|
7
|
+
class IdentityConverter < BaseConverter
|
|
8
|
+
def initialize(target_automation)
|
|
9
|
+
super()
|
|
10
|
+
@target_automation = target_automation
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def convert_page(content, _page_info)
|
|
14
|
+
result = content.dup
|
|
15
|
+
result = ensure_frozen_string_literal(result)
|
|
16
|
+
result = update_base_class(result)
|
|
17
|
+
result = add_page_require(result)
|
|
18
|
+
result = update_page_instantiation(result)
|
|
19
|
+
result
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def convert_test(content, _test_info)
|
|
23
|
+
result = content.dup
|
|
24
|
+
result = ensure_frozen_string_literal(result)
|
|
25
|
+
result = update_require_paths(result, @target_automation)
|
|
26
|
+
result = update_page_instantiation(result)
|
|
27
|
+
result
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def convert_step(content)
|
|
31
|
+
result = content.dup
|
|
32
|
+
result = ensure_frozen_string_literal(result)
|
|
33
|
+
result = update_require_paths(result, @target_automation)
|
|
34
|
+
result = update_page_instantiation(result)
|
|
35
|
+
result
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def add_page_require(content)
|
|
41
|
+
return content if content.include?("require_relative '../abstract/page'")
|
|
42
|
+
|
|
43
|
+
content.sub(/^(class\s)/, "require_relative '../abstract/page'\n\n\\1")
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def update_page_instantiation(content)
|
|
47
|
+
arg = driver_arg_for(@target_automation)
|
|
48
|
+
if arg
|
|
49
|
+
swap_driver_arg(content, arg)
|
|
50
|
+
else
|
|
51
|
+
remove_driver_arg(content)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Adopter
|
|
4
|
+
class MigrationPlan
|
|
5
|
+
attr_reader :source_path, :output_path, :target_automation, :target_framework,
|
|
6
|
+
:ci_platform, :skeleton_structure, :converted_pages, :converted_tests,
|
|
7
|
+
:converted_features, :converted_steps, :gemfile_additions,
|
|
8
|
+
:config_overrides, :warnings, :manual_actions
|
|
9
|
+
|
|
10
|
+
# :reek:LongParameterList { enabled: false }
|
|
11
|
+
def initialize(attrs = {})
|
|
12
|
+
@source_path = attrs[:source_path]
|
|
13
|
+
@output_path = attrs[:output_path]
|
|
14
|
+
@target_automation = attrs[:target_automation]
|
|
15
|
+
@target_framework = attrs[:target_framework]
|
|
16
|
+
@ci_platform = attrs[:ci_platform]
|
|
17
|
+
@skeleton_structure = attrs[:skeleton_structure] || {}
|
|
18
|
+
@converted_pages = attrs[:converted_pages] || []
|
|
19
|
+
@converted_tests = attrs[:converted_tests] || []
|
|
20
|
+
@converted_features = attrs[:converted_features] || []
|
|
21
|
+
@converted_steps = attrs[:converted_steps] || []
|
|
22
|
+
@gemfile_additions = attrs[:gemfile_additions] || []
|
|
23
|
+
@config_overrides = attrs[:config_overrides] || {}
|
|
24
|
+
@warnings = attrs[:warnings] || []
|
|
25
|
+
@manual_actions = attrs[:manual_actions] || []
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def to_h
|
|
29
|
+
{
|
|
30
|
+
source_path:,
|
|
31
|
+
output_path:,
|
|
32
|
+
target_automation:,
|
|
33
|
+
target_framework:,
|
|
34
|
+
ci_platform:,
|
|
35
|
+
skeleton_structure:,
|
|
36
|
+
converted_pages: converted_pages.map(&:to_h),
|
|
37
|
+
converted_tests: converted_tests.map(&:to_h),
|
|
38
|
+
converted_features: converted_features.map(&:to_h),
|
|
39
|
+
converted_steps: converted_steps.map(&:to_h),
|
|
40
|
+
gemfile_additions:,
|
|
41
|
+
config_overrides:,
|
|
42
|
+
warnings:,
|
|
43
|
+
manual_actions:
|
|
44
|
+
}
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def to_json(*args)
|
|
48
|
+
require 'json'
|
|
49
|
+
to_h.to_json(*args)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def summary
|
|
53
|
+
{
|
|
54
|
+
pages: converted_pages.length,
|
|
55
|
+
tests: converted_tests.length,
|
|
56
|
+
features: converted_features.length,
|
|
57
|
+
steps: converted_steps.length,
|
|
58
|
+
custom_gems: gemfile_additions.length,
|
|
59
|
+
warnings: warnings.length,
|
|
60
|
+
manual_actions: manual_actions.length
|
|
61
|
+
}
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
ConvertedFile = Struct.new(:output_path, :content, :source_file, :conversion_notes, keyword_init: true) do
|
|
66
|
+
def to_h
|
|
67
|
+
{
|
|
68
|
+
output_path:,
|
|
69
|
+
content:,
|
|
70
|
+
source_file:,
|
|
71
|
+
conversion_notes:
|
|
72
|
+
}
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'yaml'
|
|
5
|
+
require_relative '../generators/invoke_generators'
|
|
6
|
+
|
|
7
|
+
module Adopter
|
|
8
|
+
class Migrator
|
|
9
|
+
attr_reader :plan, :results
|
|
10
|
+
|
|
11
|
+
def initialize(plan)
|
|
12
|
+
@plan = plan
|
|
13
|
+
@results = { pages: 0, tests: 0, features: 0, steps: 0, errors: [] }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def execute
|
|
17
|
+
generate_skeleton
|
|
18
|
+
write_converted_pages
|
|
19
|
+
write_converted_tests
|
|
20
|
+
write_converted_features
|
|
21
|
+
write_converted_steps
|
|
22
|
+
merge_gemfile
|
|
23
|
+
apply_config_overrides
|
|
24
|
+
results
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
# Phase A: Generate a standard Raider project skeleton
|
|
30
|
+
def generate_skeleton
|
|
31
|
+
InvokeGenerators.generate_framework(plan.skeleton_structure)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Phase B: Overlay converted user files on top of the skeleton
|
|
35
|
+
|
|
36
|
+
def write_converted_pages
|
|
37
|
+
plan.converted_pages.each do |file|
|
|
38
|
+
write_file(file)
|
|
39
|
+
@results[:pages] += 1
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def write_converted_tests
|
|
44
|
+
plan.converted_tests.each do |file|
|
|
45
|
+
write_file(file)
|
|
46
|
+
@results[:tests] += 1
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def write_converted_features
|
|
51
|
+
plan.converted_features.each do |file|
|
|
52
|
+
write_file(file)
|
|
53
|
+
@results[:features] += 1
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def write_converted_steps
|
|
58
|
+
plan.converted_steps.each do |file|
|
|
59
|
+
write_file(file)
|
|
60
|
+
@results[:steps] += 1
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def write_file(converted_file)
|
|
65
|
+
FileUtils.mkdir_p(File.dirname(converted_file.output_path))
|
|
66
|
+
File.write(converted_file.output_path, converted_file.content)
|
|
67
|
+
rescue StandardError => e
|
|
68
|
+
@results[:errors] << "Failed to write #{converted_file.output_path}: #{e.message}"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def merge_gemfile
|
|
72
|
+
return if plan.gemfile_additions.empty?
|
|
73
|
+
|
|
74
|
+
gemfile_path = "#{plan.output_path}/Gemfile"
|
|
75
|
+
return unless File.exist?(gemfile_path)
|
|
76
|
+
|
|
77
|
+
content = File.read(gemfile_path)
|
|
78
|
+
additions = plan.gemfile_additions.reject { |gem| content.include?("'#{gem}'") }
|
|
79
|
+
return if additions.empty?
|
|
80
|
+
|
|
81
|
+
gem_lines = additions.map { |gem| "gem '#{gem}'" }.join("\n")
|
|
82
|
+
File.write(gemfile_path, "#{content}\n# Gems from source project\n#{gem_lines}\n")
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def apply_config_overrides
|
|
86
|
+
return if plan.config_overrides.empty?
|
|
87
|
+
|
|
88
|
+
config_path = "#{plan.output_path}/config/config.yml"
|
|
89
|
+
return unless File.exist?(config_path)
|
|
90
|
+
|
|
91
|
+
config = YAML.safe_load(File.read(config_path), permitted_classes: [Symbol]) || {}
|
|
92
|
+
plan.config_overrides.each { |key, value| config[key.to_s] = value }
|
|
93
|
+
File.write(config_path, YAML.dump(config))
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|